Parcial final series de tiempo#
Jeffry Jerena y David Lizcano#
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from tabulate import tabulate
EDA#
# Ruta del archivo CSV
BHdata = "C:\\Users\\Maestriadatos\\Documents\\lizcanollerenaparcial\\docs\\bitcoinhistoricaldata.csv"
# Cargar los datos
df = pd.read_csv(BHdata)
# Mostrar los primeros 5 registros para visualizar los datos
print("Primeros 5 registros:")
print(df.head())
# Mostrar la información de los tipos de datos
print("\nTipos de datos por columna:")
print(df.dtypes)
Primeros 5 registros:
Date Price Open High Low Vol. Change %
0 03/24/2024 67,211.9 64,036.5 67,587.8 63,812.9 65.59K 4.96%
1 03/23/2024 64,037.8 63,785.6 65,972.4 63,074.9 35.11K 0.40%
2 03/22/2024 63,785.5 65,501.5 66,633.3 62,328.3 72.43K -2.62%
3 03/21/2024 65,503.8 67,860.0 68,161.7 64,616.1 75.26K -3.46%
4 03/20/2024 67,854.0 62,046.8 68,029.5 60,850.9 133.53K 9.35%
Tipos de datos por columna:
Date object
Price object
Open object
High object
Low object
Vol. object
Change % object
dtype: object
# Convertir la columna 'Date' a tipo datetime
df['Date'] = pd.to_datetime(df['Date'], format='%m/%d/%Y')
# Convertir la columna 'Price', 'Open', 'High', 'Low' eliminando comas y convirtiendo a float
df['Price'] = df['Price'].str.replace(',', '').astype(float)
df['Open'] = df['Open'].str.replace(',', '').astype(float)
df['High'] = df['High'].str.replace(',', '').astype(float)
df['Low'] = df['Low'].str.replace(',', '').astype(float)
# Convertir la columna 'Vol.' eliminando comas, reemplazando 'K' por 'e3' (miles) y 'M' por 'e6' (millones), luego convertir a numérico
df['Vol.'] = df['Vol.'].str.replace('K', 'e3').str.replace('M', 'e6').str.replace(',', '')
df['Vol.'] = pd.to_numeric(df['Vol.'], errors='coerce')
# Convertir la columna 'Change %' eliminando el símbolo '%' y convirtiendo a float
df['Change %'] = df['Change %'].str.replace('%', '').astype(float)
# Mostrar los primeros 5 registros después de la conversión
print("Datos después de la conversión a numérico:")
print(df.head())
# Mostrar los tipos de datos después de la conversión
print("\nTipos de datos después de la conversión:")
print(df.dtypes)
Datos después de la conversión a numérico:
Date Price Open High Low Vol. Change %
0 2024-03-24 67211.9 64036.5 67587.8 63812.9 65590.0 4.96
1 2024-03-23 64037.8 63785.6 65972.4 63074.9 35110.0 0.40
2 2024-03-22 63785.5 65501.5 66633.3 62328.3 72430.0 -2.62
3 2024-03-21 65503.8 67860.0 68161.7 64616.1 75260.0 -3.46
4 2024-03-20 67854.0 62046.8 68029.5 60850.9 133530.0 9.35
Tipos de datos después de la conversión:
Date datetime64[ns]
Price float64
Open float64
High float64
Low float64
Vol. float64
Change % float64
dtype: object
# Análisis descriptivo con formato estético incluyendo 'dtype'
def descriptive_analysis(df):
print("Análisis Descriptivo")
# Resumen estadístico con tipo de dato
summary = df.describe().T # Transponemos para mostrar variables como filas
# Añadir una columna con el tipo de dato (dtype)
summary['dtype'] = df.dtypes
print(tabulate(summary, headers="keys", tablefmt="pretty"))
# Ejecutar el análisis descriptivo con presentación mejorada y tipo de datos
descriptive_analysis(df)
Análisis Descriptivo
+----------+--------+---------------------+---------------------+---------------------+---------------------+---------------------+---------------------+--------------------+----------------+
| | count | mean | min | 25% | 50% | 75% | max | std | dtype |
+----------+--------+---------------------+---------------------+---------------------+---------------------+---------------------+---------------------+--------------------+----------------+
| Date | 4999 | 2017-05-21 00:00:00 | 2010-07-18 00:00:00 | 2013-12-18 12:00:00 | 2017-05-21 00:00:00 | 2020-10-21 12:00:00 | 2024-03-24 00:00:00 | nan | datetime64[ns] |
| Price | 4999.0 | 10812.283336667333 | 0.1 | 226.7 | 1975.1 | 15450.150000000001 | 73066.3 | 15892.032468245246 | float64 |
| Open | 4999.0 | 10798.901840368073 | 0.0 | 226.45 | 1962.0 | 15372.15 | 73066.7 | 15872.785452462605 | float64 |
| High | 4999.0 | 11067.453050610124 | 0.1 | 231.4 | 2048.4 | 15954.55 | 73740.9 | 16270.707593648698 | float64 |
| Low | 4999.0 | 10511.891198239648 | 0.0 | 221.95 | 1875.3 | 14557.2 | 71338.4 | 15444.928770647624 | float64 |
| Vol. | 4977.0 | 4520315.993570424 | 80.0 | 31710.0 | 68380.0 | 181810.0 | 752840000.0 | 44956402.24859013 | float64 |
| Change % | 4999.0 | 0.41575115023004605 | -57.21 | -1.17 | 0.0 | 1.7850000000000001 | 336.84 | 7.094771253715175 | float64 |
+----------+--------+---------------------+---------------------+---------------------+---------------------+---------------------+---------------------+--------------------+----------------+
Identificar si existen datos faltantes#
# Verificar si existen datos faltantes en el DataFrame
print("Datos faltantes por columna:")
print(df.isnull().sum())
Datos faltantes por columna:
Date 0
Price 0
Open 0
High 0
Low 0
Vol. 22
Change % 0
dtype: int64
Imputación de datos faltantes#
# Aplicar la técnica de imputación (forward fill) para rellenar los valores nulos
df.fillna(method='ffill', inplace=True)
# Verificar si se han rellenado los datos faltantes
print("Datos faltantes por columna después de la imputación:")
print(df.isnull().sum())
Datos faltantes por columna después de la imputación:
Date 0
Price 0
Open 0
High 0
Low 0
Vol. 0
Change % 0
dtype: int64
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_10416\1401077761.py:2: FutureWarning: DataFrame.fillna with 'method' is deprecated and will raise in a future version. Use obj.ffill() or obj.bfill() instead.
df.fillna(method='ffill', inplace=True)
Gráfico de velas (Candlestick chart)#
Gráfico de velas: Visualiza el comportamiento de los precios (apertura, máximo, mínimo, cierre) a lo largo del tiempo.
import plotly.graph_objects as go
# Crear gráfico de velas (candlestick chart)
def plot_candlestick(df):
fig = go.Figure(data=[go.Candlestick(x=df['Date'],
open=df['Open'],
high=df['High'],
low=df['Low'],
close=df['Price'],
name='Candlestick')])
# Configuración del título y los ejes
fig.update_layout(
title='Gráfico de Velas del Precio de Bitcoin',
xaxis_title='Fecha',
yaxis_title='Precio (USD)',
xaxis_rangeslider_visible=False, # Ocultar el slider
template='plotly_white' # Estilo limpio
)
# Mostrar el gráfico
fig.show()
# Ejecutar el gráfico de velas
plot_candlestick(df)
Vemos una tendencia creciente en el precio del Bitcoin
Histograma del volumen#
Histograma del volumen: Muestra la cantidad de volumen tradeado diariamente de forma clara e interactiva.
import plotly.express as px
# Crear el histograma del volumen tradeado diariamente
def plot_volume_histogram(df):
fig = px.histogram(df, x='Date', y='Vol.', nbins=50, title='Histograma del Volumen Tradeado Diariamente')
# Configuración del diseño del histograma
fig.update_layout(
xaxis_title='Fecha',
yaxis_title='Volumen Tradeado (USD)',
template='plotly_white', # Estilo limpio
bargap=0.1 # Ajustar el espacio entre barras
)
# Mostrar el gráfico
fig.show()
# Ejecutar el histograma del volumen
plot_volume_histogram(df)
El volumen de tradeo del Bitcoin fue significativamente mas grande en 2022 que el resto de anos, esto puede explicar su precio maximo en esta epoca
Gráficos de Subseries: Gráficos de Series de Tiempo#
Para crear gráficos de subseries agrupados por semana, mes y año, utilizando gráficos de series de tiempo y boxplots como se menciona en la imagen, puedes seguir los siguientes pasos:
Agrupación de datos por semana, mes y año: Primero, agruparemos los datos por semana, mes y año usando la columna Date para extraer las subseries. Luego, podemos generar gráficos de series temporales y boxplots.
Gráficos de series de tiempo: Estos gráficos mostrarán la evolución de los precios (o cualquier otra variable) a lo largo de las subseries (por semana, mes, año).
Boxplots: Los boxplots son útiles para comparar la distribución de los precios (o el volumen) dentro de los grupos definidos (semanal, mensual, anual).
Código paso a paso:
Paso 1: Agrupar por semana, mes y año
import pandas as pd
# Asegúrate de que 'Date' sea una columna de tipo datetime
df['Date'] = pd.to_datetime(df['Date'])
# Agregar columnas para semana, mes y año
df['Week'] = df['Date'].dt.isocalendar().week
df['Month'] = df['Date'].dt.month
df['Year'] = df['Date'].dt.year
Paso 2: Gráficos de series temporales por subseries
import plotly.express as px
# Gráfico de series de tiempo por semana
def plot_time_series_week(df):
fig = px.line(df, x='Date', y='Price', color='Week',
title='Precio de Bitcoin por Semana',
labels={'Price':'Precio (USD)', 'Date':'Fecha', 'Week':'Semana'})
fig.show()
# Ejecutar el gráfico de series de tiempo semanal
plot_time_series_week(df)
# Gráfico de series de tiempo por mes
def plot_time_series_month(df):
fig = px.line(df, x='Date', y='Price', color='Month',
title='Precio de Bitcoin por Mes',
labels={'Price':'Precio (USD)', 'Date':'Fecha', 'Month':'Mes'})
fig.show()
# Ejecutar el gráfico de series de tiempo mensual
plot_time_series_month(df)
# Gráfico de series de tiempo por año
def plot_time_series_year(df):
fig = px.line(df, x='Date', y='Price', color='Year',
title='Precio de Bitcoin por Año',
labels={'Price':'Precio (USD)', 'Date':'Fecha', 'Year':'Año'})
fig.show()
# Ejecutar el gráfico de series de tiempo anual
plot_time_series_year(df)
Analizando el grafico de la division anual, semanal y mensual solo podemos afirmar que el precio del bitcoin pese a ser un activo volatil, sigue una tendencia a traves de periodos anuales
Gráficos de Subseries: Boxplots para analizar las subseries#
# Boxplot de precios por semana
def plot_boxplot_week(df):
fig = px.box(df, x='Week', y='Price', title='Distribución del Precio de Bitcoin por Semana',
labels={'Price':'Precio (USD)', 'Week':'Semana'})
fig.show()
# Ejecutar el boxplot semanal
plot_boxplot_week(df)
# Boxplot de precios por mes
def plot_boxplot_month(df):
fig = px.box(df, x='Month', y='Price', title='Distribución del Precio de Bitcoin por Mes',
labels={'Price':'Precio (USD)', 'Month':'Mes'})
fig.show()
# Ejecutar el boxplot mensual
plot_boxplot_month(df)
# Boxplot de precios por año
def plot_boxplot_year(df):
fig = px.box(df, x='Year', y='Price', title='Distribución del Precio de Bitcoin por Año',
labels={'Price':'Precio (USD)', 'Year':'Año'})
fig.show()
# Ejecutar el boxplot anual
plot_boxplot_year(df)
Observamos que la distribucion en boxplot por mes, ano y semana nos dice que realmente a traves de los anos el precio del bitcoin ha cambiado bastante. Siendo estos ultimos anos relativamente alto pero en sus primeros anos relativamente bajo
Explicación de cada parte: Agrupación: Las columnas adicionales para semana, mes y año permiten segmentar las series de tiempo en diferentes subseries. Gráficos de series de tiempo: Estos gráficos permiten ver cómo evoluciona el precio de Bitcoin dentro de cada subserie (semana, mes o año). Boxplots: Los boxplots permiten analizar la dispersión y distribución de los precios en las diferentes subseries.
Estudio de Estacionalidad#
Para estudiar la estacionariedad de una serie de tiempo utilizando la función de autocorrelación (ACF), el test de Ljung-Box y el test de Dickey-Fuller, podemos seguir los siguientes pasos. También te mostraré cómo aplicar las transformaciones necesarias para convertir la serie en estacionaria si es necesario.
Paso 1: Verificar la estacionariedad con la ACF (Autocorrelation Function) La ACF nos ayuda a observar si existe correlación en los rezagos (lags) de la serie temporal.
Paso 2: Realizar los test estadísticos Test de Ljung-Box: Evalúa si los valores de autocorrelación en todos los rezagos son cercanos a cero, lo que indicaría que no hay correlación y, por lo tanto, la serie es estacionaria. Test de Dickey-Fuller aumentado: Evalúa la hipótesis nula de que una serie tiene una raíz unitaria, lo que implica que no es estacionaria. Paso 3: Transformar la serie de tiempo Si los resultados indican que la serie no es estacionaria, aplicaremos transformaciones como la diferenciación para convertir la serie en estacionaria.
Código paso a paso Importar las librerías necesarias:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from statsmodels.graphics.tsaplots import plot_acf
from statsmodels.tsa.stattools import adfuller, acf, q_stat
from statsmodels.stats.diagnostic import acorr_ljungbox
8.1 Gráfico de ACF para verificar autocorrelación
# Gráfico de autocorrelación (ACF) para la columna 'Price'
def plot_acf_chart(df):
plt.figure(figsize=(10, 6))
plot_acf(df['Price'], lags=40) # Mostrar los primeros 40 rezagos
plt.title('Función de Autocorrelación (ACF) del Precio de Bitcoin')
plt.show()
# Ejecutar el gráfico de ACF
plot_acf_chart(df)
<Figure size 1000x600 with 0 Axes>
En el gráfico que has generado, la Función de Autocorrelación (ACF) muestra una alta autocorrelación significativa en todos los rezagos (lags), lo cual es un indicador de que la serie no es estacionaria. En una serie estacionaria, las autocorrelaciones deberían disminuir rápidamente a medida que aumenta el número de rezagos, mientras que aquí todas las barras están cerca de 1, lo que indica una fuerte correlación a lo largo del tiempo.
Próximos pasos: Test de Dickey-Fuller: Este test confirmará si la serie tiene una raíz unitaria (no estacionaria). Transformación de la serie: Si la serie no es estacionaria, podemos aplicar la diferenciación para convertirla en estacionaria.
8.2 Test de Ljung-Box
# Test de Ljung-Box para verificar si la serie es autocorrelacionada
def ljung_box_test(df):
lb_test = acorr_ljungbox(df['Price'], lags=[10], return_df=True)
print("Resultados del Test de Ljung-Box:")
print(lb_test)
# Ejecutar el test de Ljung-Box
ljung_box_test(df)
Resultados del Test de Ljung-Box:
lb_stat lb_pvalue
10 48871.328172 0.0
Interpretación: Un valor p de 0.0 indica que rechazamos la hipótesis nula de que no hay autocorrelación en los rezagos. Esto significa que hay autocorrelación significativa en la serie temporal, lo que sugiere que no es estacionaria.
Interpretación: Un valor p de 0.0 indica que rechazamos la hipótesis nula de que no hay autocorrelación en los rezagos. Esto significa que hay autocorrelación significativa en la serie temporal, lo que sugiere que no es estacionaria.
# Test de Dickey-Fuller aumentado
def adfuller_test(df):
result = adfuller(df['Price'])
print('Resultados del Test de Dickey-Fuller:')
print(f'Estadístico ADF: {result[0]}')
print(f'Valor P: {result[1]}')
print(f'Valores Críticos:')
for key, value in result[4].items():
print(f' {key}: {value}')
# Ejecutar el test de Dickey-Fuller
adfuller_test(df)
Resultados del Test de Dickey-Fuller:
Estadístico ADF: -2.685797950257254
Valor P: 0.07652825121387355
Valores Críticos:
1%: -3.4316674956516784
5%: -2.8621221897344484
10%: -2.567079900452355
Interpretación: El valor p es 0.0756, que es mayor a 0.05, lo que significa que no podemos rechazar la hipótesis nula de que la serie tiene una raíz unitaria. Esto implica que la serie no es estacionaria.
Además, el estadístico ADF (-2.68) no es más pequeño que los valores críticos a los niveles del 1%, 5%, o incluso el 10%, lo que refuerza la conclusión de que la serie no es estacionaria.
Transformar la serie para hacerla estacionaria
from statsmodels.tsa.seasonal import seasonal_decompose
import matplotlib.pyplot as plt
# Asegurarse de que los datos están ordenados por fecha
df = df.sort_values(by='Date')
# Descomponer la serie en tendencia, estacionalidad y residuo
decomposed = seasonal_decompose(df['Price'], model='multiplicative', period=30) # Ajustar 'period' según el ciclo
# Graficar los componentes
decomposed.plot()
plt.show()
from statsmodels.graphics.tsaplots import plot_acf
from statsmodels.tsa.stattools import adfuller
# Graficar la ACF de los residuos
plot_acf(decomposed.resid.dropna(), lags=40)
plt.title('ACF de los Residuos')
plt.show()
# Aplicar el Test de Dickey-Fuller en los residuos
result_adf = adfuller(decomposed.resid.dropna())
print(f'Estadístico ADF: {result_adf[0]}')
print(f'Valor P: {result_adf[1]}')
print(f'Valores Críticos: {result_adf[4]}')
Estadístico ADF: -15.674313207396887
Valor P: 1.499403070029146e-28
Valores Críticos: {'1%': np.float64(-3.4316752386670926), '5%': np.float64(-2.86212561054255), '10%': np.float64(-2.5670817214845068)}
Agregación por grupos (semanas, días, meses) y Cálculo de estadísticas móviles**#
Agregacion por grupos:
# Asegúrate de que la columna 'Date' esté en formato datetime
df['Date'] = pd.to_datetime(df['Date'])
# Agregar por semana
weekly_data = df.resample('W', on='Date').agg({'Price': ['mean', 'std']})
# Agregar por mes
monthly_data = df.resample('M', on='Date').agg({'Price': ['mean', 'std']})
# Agregar por día
daily_data = df.resample('D', on='Date').agg({'Price': ['mean', 'std']})
# Mostrar el resultado
print("Datos semanales (media y desviación estándar):")
print(weekly_data.head())
print("\nDatos mensuales (media y desviación estándar):")
print(monthly_data.head())
print("\nDatos diarios (media y desviación estándar):")
print(daily_data.head())
Datos semanales (media y desviación estándar):
Price
mean std
Date
2010-07-18 0.1 NaN
2010-07-25 0.1 0.0
2010-08-01 0.1 0.0
2010-08-08 0.1 0.0
2010-08-15 0.1 0.0
Datos mensuales (media y desviación estándar):
Price
mean std
Date
2010-07-31 0.100000 0.000000
2010-08-31 0.100000 0.000000
2010-09-30 0.100000 0.000000
2010-10-31 0.119355 0.040161
2010-11-30 0.263333 0.055605
Datos diarios (media y desviación estándar):
Price
mean std
Date
2010-07-18 0.1 NaN
2010-07-19 0.1 NaN
2010-07-20 0.1 NaN
2010-07-21 0.1 NaN
2010-07-22 0.1 NaN
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_10416\1942824804.py:8: FutureWarning:
'M' is deprecated and will be removed in a future version, please use 'ME' instead.
Comportamiento de la media y desviación estándar
# Convertir la columna 'Date' a tipo datetime
df['Date'] = pd.to_datetime(df['Date'], format='%Y-%m-%d')
# Agrupar los datos por semana, mes y año y calcular la media y la desviación estándar
weekly_data = df.resample('W', on='Date').agg({'Price': ['mean', 'std']})
monthly_data = df.resample('M', on='Date').agg({'Price': ['mean', 'std']})
annual_data = df.resample('Y', on='Date').agg({'Price': ['mean', 'std']})
# Graficar el comportamiento de la media y desviación estándar por semanas
plt.figure(figsize=(10, 6))
plt.plot(weekly_data.index, weekly_data['Price']['mean'], label='Media Semanal', color='blue')
plt.fill_between(weekly_data.index, weekly_data['Price']['mean'] - weekly_data['Price']['std'],
weekly_data['Price']['mean'] + weekly_data['Price']['std'], color='blue', alpha=0.2)
plt.title('Comportamiento de la Media y Desviación Estándar Semanal')
plt.xlabel('Fecha')
plt.ylabel('Precio')
plt.legend()
plt.xticks(rotation=45)
plt.show()
# Graficar para los datos mensuales
plt.figure(figsize=(10, 6))
plt.plot(monthly_data.index, monthly_data['Price']['mean'], label='Media Mensual', color='green')
plt.fill_between(monthly_data.index, monthly_data['Price']['mean'] - monthly_data['Price']['std'],
monthly_data['Price']['mean'] + monthly_data['Price']['std'], color='green', alpha=0.2)
plt.title('Comportamiento de la Media y Desviación Estándar Mensual')
plt.xlabel('Fecha')
plt.ylabel('Precio')
plt.legend()
plt.xticks(rotation=45)
plt.show()
# Graficar el comportamiento de la media y desviación estándar anual
plt.figure(figsize=(10, 6))
plt.plot(annual_data.index, annual_data['Price']['mean'], label='Media Anual', color='purple')
plt.fill_between(annual_data.index, annual_data['Price']['mean'] - annual_data['Price']['std'],
annual_data['Price']['mean'] + annual_data['Price']['std'], color='purple', alpha=0.2)
plt.title('Comportamiento de la Media y Desviación Estándar Anual')
plt.xlabel('Fecha')
plt.ylabel('Precio')
plt.legend()
plt.xticks(rotation=45)
plt.show()
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_10416\3000577512.py:6: FutureWarning:
'M' is deprecated and will be removed in a future version, please use 'ME' instead.
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_10416\3000577512.py:7: FutureWarning:
'Y' is deprecated and will be removed in a future version, please use 'YE' instead.
En estas graficas vemos como pese a que la desviacion standard diaria no es alta, la anual gracias a la gran robustez de datos si es mayor
Estadisticos Moviles
# Media y desviación estándar móviles con ventana de 30 días
df['Mean_Mobile'] = df['Price'].rolling(window=30).mean()
df['Std_Mobile'] = df['Price'].rolling(window=30).std()
# Graficar la media móvil y la desviación estándar móvil
plt.figure(figsize=(10, 6))
plt.plot(df['Date'], df['Mean_Mobile'], label='Media Móvil (30 días)', color='blue')
plt.fill_between(df['Date'], df['Mean_Mobile'] - df['Std_Mobile'],
df['Mean_Mobile'] + df['Std_Mobile'], color='blue', alpha=0.2)
plt.title('Media Móvil y Desviación Estándar Móvil (30 días)')
plt.xlabel('Fecha')
plt.ylabel('Precio')
plt.legend()
plt.xticks(rotation=45)
plt.show()
Distribucion de frecuencia para diferentes lags
import seaborn as sns
import matplotlib.pyplot as plt
# Diferencias con diferentes lags
df['Price_diff_1'] = df['Price'].diff(1) # Lag de 1 día
df['Price_diff_7'] = df['Price'].diff(7) # Lag de 1 semana
df['Price_diff_30'] = df['Price'].diff(30) # Lag de 1 mes
# Graficar la distribución de las diferencias para diferentes lags
plt.figure(figsize=(10, 6))
sns.histplot(df['Price_diff_1'].dropna(), label='Lag 1 día', color='blue', kde=True)
sns.histplot(df['Price_diff_7'].dropna(), label='Lag 7 días', color='green', kde=True)
sns.histplot(df['Price_diff_30'].dropna(), label='Lag 30 días', color='red', kde=True)
plt.title('Distribución de Frecuencias para Diferentes Lags')
plt.xlabel('Diferencias de Precio')
plt.ylabel('Frecuencia')
plt.ylim(0, 1100) # Ajustar el eje Y a un máximo de 1100
plt.legend()
plt.show()
Aplicación de las medias móviles#
Medias móviles: Se calculan medias móviles para diferentes ventanas (2, 3, 4 días) y se comparan con la serie original.
Remover tendencia: La serie se suaviza con una media móvil y se elimina la tendencia restando la media móvil de la serie original.
Pruebas de estacionariedad: Aplicamos el test de Dickey-Fuller y el test de Ljung-Box para verificar si la serie sin tendencia es estacionaria.
Aplicamos las medias moviles
# Medias móviles de diferentes órdenes
df['M2'] = df['Price'].rolling(window=2).mean()
df['M4'] = df['Price'].rolling(window=4).mean()
df['M3'] = df['Price'].rolling(window=3).mean()
# Visualizar las medias móviles junto con la serie original
plt.figure(figsize=(10, 6))
plt.plot(df['Date'], df['Price'], label='Precio Original', color='black')
plt.plot(df['Date'], df['M2'], label='Media Móvil (2 días)', color='blue')
plt.plot(df['Date'], df['M4'], label='Media Móvil (4 días)', color='green')
plt.plot(df['Date'], df['M3'], label='Media Móvil (3 días)', color='red')
plt.title('Medias Móviles de Diferentes Ventanas')
plt.xlabel('Fecha')
plt.ylabel('Precio')
plt.legend()
plt.xticks(rotation=45)
plt.show()
Removemos la tendencia con la media movil
# Remover la tendencia restando la media móvil de 4 días de la serie original
df['Detrended'] = df['Price'] - df['M4']
# Graficar la serie sin tendencia
plt.figure(figsize=(10, 6))
plt.plot(df['Date'], df['Detrended'], label='Serie sin Tendencia', color='purple')
plt.title('Serie de Precios sin Tendencia (Media Móvil 4 días)')
plt.xlabel('Fecha')
plt.ylabel('Precio Detrended')
plt.legend()
plt.xticks(rotation=45)
plt.show()
Pruebas de estacionariedad (Dickey-Fuller y Ljung-Box):
from statsmodels.tsa.stattools import adfuller
from statsmodels.stats.diagnostic import acorr_ljungbox
# Aplicar la prueba de Dickey-Fuller en la serie sin tendencia
adf_result = adfuller(df['Detrended'].dropna())
print(f'Estadístico ADF: {adf_result[0]}')
print(f'Valor P: {adf_result[1]}')
# Aplicar la prueba de Ljung-Box en la serie sin tendencia
ljungbox_result = acorr_ljungbox(df['Detrended'].dropna(), lags=[10], return_df=True)
print(ljungbox_result)
Estadístico ADF: -9.249080004579481
Valor P: 1.5093100712032719e-15
lb_stat lb_pvalue
10 1894.301002 0.0
Prueba de Dickey-Fuller (ADF):
Interpretación: Dado que el valor p es mucho menor a 0.05, puedes rechazar la hipótesis nula de que la serie tiene una raíz unitaria, lo que significa que la serie sin tendencia es estacionaria.
Prueba de Ljung-Box:
Interpretación: Un valor p de 0.0 en la prueba de Ljung-Box sugiere que hay autocorrelación significativa en los residuos. Esto significa que, aunque la serie sea estacionaria según la prueba de Dickey-Fuller, aún hay dependencia temporal (autocorrelación) que debe ser explicada o modelada.
Descomposición de la serie temporal del precio de cierre de BTC-USD#
Descomposicion con medias moviles
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.tsa.seasonal import seasonal_decompose
# Aplicar media móvil de 7 días para suavizar
df['Price_MA7'] = df['Price'].rolling(window=7).mean()
# Graficar la serie original y la suavizada
plt.figure(figsize=(10, 6))
plt.plot(df['Date'], df['Price'], label='Precio Original', color='black')
plt.plot(df['Date'], df['Price_MA7'], label='Media Móvil 7 días', color='blue')
plt.title('Precio Original vs. Media Móvil (7 días)')
plt.xlabel('Fecha')
plt.ylabel('Precio')
plt.legend()
plt.xticks(rotation=45)
plt.show()
# Descomposición de la serie temporal usando statsmodels.tsa
decomposed = seasonal_decompose(df['Price_MA7'].dropna(), model='additive', period=30)
# Graficar la descomposición
decomposed.plot()
plt.show()
Verificación de la independencia de los residuos (Prueba de Ljung-Box)
from statsmodels.stats.diagnostic import acorr_ljungbox
# Obtener los residuos de la descomposición
residuos = decomposed.resid.dropna()
# Aplicar la prueba de Ljung-Box en los residuos
ljungbox_result = acorr_ljungbox(residuos, lags=[10], return_df=True)
# Mostrar los resultados de la prueba
print(ljungbox_result)
lb_stat lb_pvalue
10 14625.69533 0.0
El valor p extremadamente pequeño sugiere que existe autocorrelación significativa en los residuos. Esto implica que la descomposición realizada aún no ha eliminado toda la dependencia temporal en los residuos.
Analisis de la serie de BITCOIN para el retorno acumulado diario AT y las volatilidades#
import pandas as pd
df = pd.read_csv(BHdata)
# Convertir la columna 'Date' a tipo datetime
df['Date'] = pd.to_datetime(df['Date'], format='%m/%d/%Y')
# Convertir las columnas 'Price', 'Open', 'High', 'Low' eliminando comas y convirtiendo a float
df['Price'] = pd.to_numeric(df['Price'].str.replace(',', ''), errors='coerce')
df['Open'] = pd.to_numeric(df['Open'].str.replace(',', ''), errors='coerce')
df['High'] = pd.to_numeric(df['High'].str.replace(',', ''), errors='coerce')
df['Low'] = pd.to_numeric(df['Low'].str.replace(',', ''), errors='coerce')
# Convertir la columna 'Vol.' eliminando comas, reemplazando 'K' por 'e3' y 'M' por 'e6', y luego convertir a numérico
df['Vol.'] = df['Vol.'].str.replace('K', 'e3').str.replace('M', 'e6').str.replace(',', '')
df['Vol.'] = pd.to_numeric(df['Vol.'], errors='coerce')
# Convertir la columna 'Change %' eliminando el símbolo '%' y convirtiendo a float
df['Change %'] = df['Change %'].str.replace('%', '').astype(float)
# Eliminar las filas con valores nulos en la columna 'Price'
df.dropna(subset=['Price'], inplace=True)
# Calcular el retorno diario Rt
df['Rt'] = (df['Price'] - df['Price'].shift(1)) / df['Price'].shift(1)
# Calcular el retorno acumulado diario At
df['At'] = df['Rt'].cumsum()
# Función para calcular la volatilidad usando ventanas móviles
def calcular_volatilidad(data, ventana):
"""
Esta función calcula la volatilidad (desviación estándar)
de los retornos diarios utilizando una ventana móvil.
"""
return data['Rt'].rolling(window=ventana).std()
# Calcular volatilidad para diferentes ventanas
ventanas = [7, 14, 21, 28]
for ventana in ventanas:
df[f'Volatilidad_{ventana}'] = calcular_volatilidad(df, ventana)
# Mostrar los primeros 10 registros con las nuevas columnas de volatilidad
print(df[['Date', 'At', 'Volatilidad_7', 'Volatilidad_14', 'Volatilidad_21', 'Volatilidad_28']].head(15))
Date At Volatilidad_7 Volatilidad_14 Volatilidad_21 \
0 2024-03-24 NaN NaN NaN NaN
1 2024-03-23 -0.047225 NaN NaN NaN
2 2024-03-22 -0.051165 NaN NaN NaN
3 2024-03-21 -0.024226 NaN NaN NaN
4 2024-03-20 0.011652 NaN NaN NaN
5 2024-03-19 -0.073884 NaN NaN NaN
6 2024-03-18 0.015465 NaN NaN NaN
7 2024-03-17 0.027257 0.057142 NaN NaN
8 2024-03-16 -0.017734 0.056814 NaN NaN
9 2024-03-15 0.045797 0.060785 NaN NaN
10 2024-03-14 0.073492 0.060813 NaN NaN
11 2024-03-13 0.097009 0.060247 NaN NaN
12 2024-03-12 0.075165 0.046199 NaN NaN
13 2024-03-11 0.083964 0.035128 NaN NaN
14 2024-03-10 0.040492 0.040414 0.04756 NaN
Volatilidad_28
0 NaN
1 NaN
2 NaN
3 NaN
4 NaN
5 NaN
6 NaN
7 NaN
8 NaN
9 NaN
10 NaN
11 NaN
12 NaN
13 NaN
14 NaN
# Análisis descriptivo con formato estético incluyendo 'dtype'
def descriptive_analysis(df):
print("Análisis Descriptivo")
# Resumen estadístico con tipo de dato
summary = df.describe().T # Transponemos para mostrar variables como filas
# Añadir una columna con el tipo de dato (dtype)
summary['dtype'] = df.dtypes
print(tabulate(summary, headers="keys", tablefmt="pretty"))
# Ejecutar el análisis descriptivo con presentación mejorada y tipo de datos
descriptive_analysis(df)
Análisis Descriptivo
+----------------+--------+------------------------+---------------------+-----------------------+----------------------+----------------------+---------------------+----------------------+----------------+
| | count | mean | min | 25% | 50% | 75% | max | std | dtype |
+----------------+--------+------------------------+---------------------+-----------------------+----------------------+----------------------+---------------------+----------------------+----------------+
| Date | 4999 | 2017-05-21 00:00:00 | 2010-07-18 00:00:00 | 2013-12-18 12:00:00 | 2017-05-21 00:00:00 | 2020-10-21 12:00:00 | 2024-03-24 00:00:00 | nan | datetime64[ns] |
| Price | 4999.0 | 10812.283336667333 | 0.1 | 226.7 | 1975.1 | 15450.150000000001 | 73066.3 | 15892.032468245246 | float64 |
| Open | 4999.0 | 10798.901840368073 | 0.0 | 226.45 | 1962.0 | 15372.15 | 73066.7 | 15872.785452462605 | float64 |
| High | 4999.0 | 11067.453050610124 | 0.1 | 231.4 | 2048.4 | 15954.55 | 73740.9 | 16270.707593648698 | float64 |
| Low | 4999.0 | 10511.891198239648 | 0.0 | 221.95 | 1875.3 | 14557.2 | 71338.4 | 15444.928770647624 | float64 |
| Vol. | 4977.0 | 4520315.993570424 | 80.0 | 31710.0 | 68380.0 | 181810.0 | 752840000.0 | 44956402.24859013 | float64 |
| Change % | 4999.0 | 0.41575115023004605 | -57.21 | -1.17 | 0.0 | 1.7850000000000001 | 336.84 | 7.094771253715175 | float64 |
| Rt | 4998.0 | -0.0008649416107884587 | -0.7710335525206542 | -0.018043005662710842 | 0.0 | 0.012381674779143557 | 1.3369079535299373 | 0.060963078744488954 | float64 |
| At | 4998.0 | -1.7488420320226186 | -5.0729781707207176 | -2.8795951025620825 | -1.339525597017683 | -0.7968745330235159 | 0.7233894556997841 | 1.308646419197632 | float64 |
| Volatilidad_7 | 4992.0 | 0.04004272329136203 | 0.0 | 0.017841963196907742 | 0.028731243369467502 | 0.045641369317982274 | 0.7425252346415011 | 0.046675532410058646 | float64 |
| Volatilidad_14 | 4985.0 | 0.04254656625278011 | 0.0 | 0.020570255012699223 | 0.031840592334259225 | 0.04809342031022853 | 0.5133244328337819 | 0.04381810312793947 | float64 |
| Volatilidad_21 | 4978.0 | 0.04386830333712439 | 0.0 | 0.022328891704747465 | 0.0329581872933121 | 0.048341492502098536 | 0.4172879234974023 | 0.04233204152371323 | float64 |
| Volatilidad_28 | 4971.0 | 0.044800297271042205 | 0.0 | 0.023726993331419644 | 0.03345168952269009 | 0.049422096425338735 | 0.36087652079242866 | 0.041310540523075305 | float64 |
+----------------+--------+------------------------+---------------------+-----------------------+----------------------+----------------------+---------------------+----------------------+----------------+
Revision de datos faltantes y imputacion de estos#
# Verificar si existen datos faltantes en el DataFrame
print("Datos faltantes por columna:")
print(df.isnull().sum())
# Aplicar la técnica de imputación (forward fill) para rellenar los valores nulos
df.fillna(method='ffill', inplace=True)
# Verificar si se han rellenado los datos faltantes
print("Datos faltantes por columna después de la imputación:")
print(df.isnull().sum())
# Calcular los retornos y volatilidades (suponiendo que ya lo has hecho)
# Eliminar las filas con valores nulos en las columnas 'Rt', 'At' y las volatilidades
df.dropna(subset=['Rt', 'At', 'Volatilidad_7', 'Volatilidad_14', 'Volatilidad_21', 'Volatilidad_28'], inplace=True)
# Verificar si aún existen datos faltantes
print("Datos faltantes por columna después de eliminar filas con NaN:")
print(df.isnull().sum())
Datos faltantes por columna:
Date 0
Price 0
Open 0
High 0
Low 0
Vol. 22
Change % 0
Rt 1
At 1
Volatilidad_7 7
Volatilidad_14 14
Volatilidad_21 21
Volatilidad_28 28
dtype: int64
Datos faltantes por columna después de la imputación:
Date 0
Price 0
Open 0
High 0
Low 0
Vol. 0
Change % 0
Rt 1
At 1
Volatilidad_7 7
Volatilidad_14 14
Volatilidad_21 21
Volatilidad_28 28
dtype: int64
Datos faltantes por columna después de eliminar filas con NaN:
Date 0
Price 0
Open 0
High 0
Low 0
Vol. 0
Change % 0
Rt 0
At 0
Volatilidad_7 0
Volatilidad_14 0
Volatilidad_21 0
Volatilidad_28 0
dtype: int64
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_10416\3424944515.py:6: FutureWarning:
DataFrame.fillna with 'method' is deprecated and will raise in a future version. Use obj.ffill() or obj.bfill() instead.
Graficos de Velas para At#
import plotly.graph_objects as go
# Crear gráfico de velas (candlestick chart)
def plot_candlestick(df):
fig = go.Figure(data=[go.Candlestick(x=df['Date'],
open=df['Open'],
high=df['High'],
low=df['Low'],
close=df['At'],
name='Candlestick')])
# Configuración del título y los ejes
fig.update_layout(
title='Gráfico de Velas del retorno acumulado del Bitcoin',
xaxis_title='Fecha',
yaxis_title='Retorno acumulado diario (USD)',
xaxis_rangeslider_visible=False, # Ocultar el slider
template='plotly_white' # Estilo limpio
)
# Mostrar el gráfico
fig.show()
# Ejecutar el gráfico de velas
plot_candlestick(df)
Analisis del grafico de velas del AT del Bitcoin#
El gráfico de velas de los retornos acumulados de Bitcoin en estos últimos 14 años me muestra cómo ha tenido picos de crecimiento impresionantes, con subidas fuertes en momentos como 2017 y luego entre 2020 y 2021. Entre esos picos veo también caídas marcadas, como en 2018 y otra en 2022, que destacan la volatilidad de este mercado. A pesar de esas correcciones, lo que se ve es una tendencia a que, cada vez que retrocede, termina alcanzando nuevos máximos en el largo plazo.
Histograma de las Volatilidades#
import plotly.express as px
# Crear el histograma de las volatilidades
def plot_volatility_histogram(df, volatility_column):
fig = px.histogram(df, x=volatility_column, nbins=50, title=f'Histograma de {volatility_column}')
# Configuración del diseño del histograma
fig.update_layout(
xaxis_title='Volatilidad',
yaxis_title='Frecuencia',
template='plotly_white', # Estilo limpio
bargap=0.1 # Ajustar el espacio entre barras
)
# Mostrar el gráfico
fig.show()
# Ejecutar el histograma para 'Volatilidad_7', 'Volatilidad_14', 'Volatilidad_21' y 'Volatilidad_28'
volatility_columns = ['Volatilidad_7', 'Volatilidad_14', 'Volatilidad_21', 'Volatilidad_28']
for col in volatility_columns:
plot_volatility_histogram(df, col)
Analisis del histograma de las volatilidades#
Al ver estos histogramas de volatilidad de Bitcoin con ventanas de 7, 14, 21 y 28 días, noto que todos muestran una distribución similar. La mayor parte de la volatilidad se concentra en niveles bajos, especialmente entre 0 y 0.05, sin importar la longitud de la ventana. Esto me indica que, independientemente del período de observación, la volatilidad de Bitcoin tiende a mantenerse en valores reducidos la mayoría del tiempo.
Sin embargo, en todos los histogramas hay algunas observaciones en rangos más altos, alrededor de 0.05 a 0.15, que son menos frecuentes pero marcan episodios de volatilidad alta que ocurren esporádicamente. Más allá de 0.15, los valores son todavía menos comunes, lo cual es consistente en todas las ventanas. Esto sugiere que los picos de volatilidad, aunque presentes, no son la norma y se presentan como eventos aislados, manteniendo una estructura estable en los datos de volatilidad acumulada en diferentes periodos.
Graficos de subseries de tiempo para las volatilidades y para At#
import pandas as pd
import plotly.express as px
# Asegúrate de que 'Date' sea una columna de tipo datetime
df['Date'] = pd.to_datetime(df['Date'], format='%m/%d/%Y')
# Agregar columnas para semana, mes y año
df['Week'] = df['Date'].dt.isocalendar().week
df['Month'] = df['Date'].dt.month
df['Year'] = df['Date'].dt.year
# Función para graficar la serie de tiempo
def graficar_serie_tiempo(df, columna, freq, titulo):
# Agrupar por frecuencia y calcular el promedio
if freq == 'semanal':
df_grouped = df.groupby(['Year', 'Week']).mean().reset_index()
x = df_grouped['Year'].astype(str) + '-W' + df_grouped['Week'].astype(str)
elif freq == 'mensual':
df_grouped = df.groupby(['Year', 'Month']).mean().reset_index()
x = df_grouped['Year'].astype(str) + '-M' + df_grouped['Month'].astype(str)
elif freq == 'anual':
df_grouped = df.groupby('Year').mean().reset_index()
x = df_grouped['Year'].astype(str)
# Graficar
fig = px.line(df_grouped, x=x, y=columna, title=titulo)
fig.update_layout(xaxis_title='Fecha', yaxis_title=columna, template='plotly_white')
fig.show()
# Graficar cada una de las volatilidades y At en frecuencias semanales, mensuales y anuales
volatilidades = ['Volatilidad_7', 'Volatilidad_14', 'Volatilidad_21', 'Volatilidad_28', 'At']
frecuencias = ['semanal', 'mensual', 'anual']
for volatilidad in volatilidades:
for freq in frecuencias:
graficar_serie_tiempo(df, volatilidad, freq, f'{volatilidad} ({freq.capitalize()})')
Analisis de la descomposicion por tiempo de la volatilidad#
Al observar estos 12 gráficos de la serie de tiempo de la volatilidad del Bitcoin, divididos en ventanas semanal, mensual y anual, noto un patrón interesante. Conforme aumenta la ventana con la que se mide la volatilidad, esta muestra más variación y picos, especialmente en las ventanas semanales. Esto significa que en periodos cortos, la volatilidad tiende a moverse de manera más abrupta y refleja mejor los eventos inmediatos que afectan el mercado.
Sin embargo, cuando analizo la volatilidad en periodos mayores, como las ventanas mensuales y, especialmente, en las anuales, noto que la variación disminuye considerablemente. La volatilidad se suaviza y fluctúa menos, mostrando un comportamiento más estable en el largo plazo. Esto me indica que, aunque Bitcoin es un activo volátil a corto plazo, en el horizonte anual su volatilidad presenta una tendencia a estabilizarse. Este análisis resalta cómo, dependiendo de la ventana y el período de observación, puedo obtener una percepción distinta sobre la estabilidad del mercado de Bitcoin: alta variabilidad a corto plazo, pero relativa estabilidad cuando se ve en un contexto de largo plazo.
Analisis de la descomposicion por tiempo de At#
Al analizar estos cuatro gráficos de los retornos acumulados del Bitcoin, noto un patrón similar al de la volatilidad en cuanto a cómo varía según la ventana de medición. A medida que aumento la ventana para calcular los retornos acumulados, veo mayores fluctuaciones en el corto plazo, reflejando de manera más clara las subidas y caídas abruptas del mercado en periodos de tiempo más pequeños.
Sin embargo, al observar los retornos acumulados en ventanas más largas, las fluctuaciones comienzan a suavizarse, mostrando una trayectoria más estable y creciente. Esto me indica que, aunque el Bitcoin es altamente volátil en el corto plazo, en un horizonte más amplio, los retornos acumulados reflejan una tendencia más positiva y consistente. Esta comparación me permite ver que, a corto plazo, los retornos están mucho más influenciados por la volatilidad, mientras que en el largo plazo la perspectiva de crecimiento se vuelve más clara y menos afectada por las variaciones diarias o semanales.
Subseries pero con Boxplot#
import plotly.express as px
# Función ajustada para graficar box plots con cajas visibles
def graficar_boxplot(df, columna, freq, titulo):
# Agrupar por frecuencia
if freq == 'semanal':
df['Semana_Año'] = df['Date'].dt.strftime('%Y-W%U')
x = 'Semana_Año'
elif freq == 'mensual':
df['Mes_Año'] = df['Date'].dt.strftime('%Y-%m')
x = 'Mes_Año'
elif freq == 'anual':
x = 'Year'
# Graficar box plot, eliminando puntos adicionales para mostrar las cajas claramente
fig = px.box(df, x=x, y=columna, title=titulo)
fig.update_layout(
xaxis_title='Fecha',
yaxis_title=columna,
template='plotly_white'
)
fig.show()
# Graficar box plots para cada volatilidad y At en frecuencias semanales, mensuales y anuales
volatilidades = ['Volatilidad_7', 'Volatilidad_14', 'Volatilidad_21', 'Volatilidad_28', 'At']
frecuencias = ['semanal', 'mensual', 'anual']
for volatilidad in volatilidades:
for freq in frecuencias:
graficar_boxplot(df, volatilidad, freq, f'Box Plot de {volatilidad} ({freq.capitalize()})')
Analisis graficos Boxplot para la volatilidad#
Al observar estos 12 gráficos boxplot de la serie de tiempo de la volatilidad del Bitcoin, divididos en ventanas semanal, mensual y anual, noto un patrón claro en cómo la volatilidad se comporta en diferentes horizontes temporales. En los boxplots semanales, la variación es tan alta que las cajas no logran formarse bien, y lo que predomina son muchos puntos dispersos. Esto me indica que, a corto plazo, la volatilidad es extremadamente variable, con cambios bruscos y constantes que reflejan los movimientos erráticos del mercado de Bitcoin.
Cuando paso a los boxplots de ventanas mensuales, noto que aunque aún hay puntos dispersos, empieza a formarse una estructura más clara en las cajas, con una menor dispersión que en las semanas. Esto sugiere que, aunque persisten los cambios, la volatilidad empieza a estabilizarse un poco cuando se considera en un horizonte intermedio.
Finalmente, al observar los gráficos de ventanas anuales, veo cajas mucho más definidas y una dispersión notablemente menor. Aquí la variabilidad de la volatilidad se reduce considerablemente, mostrando que a mayor plazo la volatilidad se comporta de forma más estable. Este análisis me confirma que, mientras que a corto plazo el Bitcoin es altamente volátil, en periodos largos esta volatilidad se modera, lo cual es útil para tener una visión más equilibrada de su comportamiento en el largo plazo.
Analisis Boxplot para los retornos acumulados (AT)#
Al ver estos cuatro gráficos boxplot de los retornos acumulados del Bitcoin, noto un patrón similar al de la volatilidad en términos de cómo la variación cambia con la ventana de medición. En los gráficos con ventanas más cortas, los retornos acumulados muestran una alta dispersión, con muchos puntos individuales fuera de las cajas, lo cual indica una variabilidad considerable y refleja los cambios bruscos que caracterizan al Bitcoin en periodos más cortos.
A medida que se amplía la ventana, veo que los boxplots empiezan a tener cajas más definidas y menos puntos dispersos. Esto sugiere que los retornos acumulados comienzan a estabilizarse cuando se miran en horizontes más largos. En las ventanas más amplias, los boxplots muestran una estructura más compacta y una variación menor en los retornos acumulados, lo que indica que, aunque el Bitcoin puede ser muy volátil en el corto plazo, sus retornos acumulados tienden a estabilizarse en el largo plazo. Esto me da una perspectiva de que, aunque el mercado puede ser inestable a corto plazo, el crecimiento acumulado de Bitcoin muestra una trayectoria más constante y menos influida por las variaciones diarias.
Ahora haremos los graficos ACF para At y cada uno de las volatilidades#
import matplotlib.pyplot as plt
from statsmodels.graphics.tsaplots import plot_acf
# Función para graficar ACF
def graficar_acf(serie, titulo):
plt.figure(figsize=(10, 6))
plot_acf(serie.dropna(), lags=40) # Se eliminan valores nulos y se usa un lag de hasta 40 períodos
plt.title(f'ACF de {titulo}')
plt.show()
# Graficar ACF para cada volatilidad y At
series = ['Volatilidad_7', 'Volatilidad_14', 'Volatilidad_21', 'Volatilidad_28', 'At']
for serie in series:
graficar_acf(df[serie], serie)
<Figure size 1000x600 with 0 Axes>
<Figure size 1000x600 with 0 Axes>
<Figure size 1000x600 with 0 Axes>
<Figure size 1000x600 with 0 Axes>
<Figure size 1000x600 with 0 Axes>
Analisis bandas ACF para la volatilidad y retornos acumulados#
Al analizar estos cuatro gráficos de la función de autocorrelación (ACF) de la serie de tiempo de la volatilidad del Bitcoin, noto que todos muestran una espina descendente con los rezagos claramente por fuera de la banda de significancia. Esto me indica una fuerte correlación entre los valores de la volatilidad a lo largo del tiempo, lo cual sugiere que la volatilidad del Bitcoin es altamente persistente: los valores actuales están fuertemente influenciados por los valores pasados. Esta “espina” que va decayendo gradualmente es típica de una serie donde los cambios en la volatilidad tienden a prolongarse antes de disminuir, mostrando una memoria larga en la serie.
Por otro lado, el gráfico de ACF del retorno acumulado es aún más extremo, con todos los rezagos alineados de manera recta y significativamente por encima de la banda de significancia, alcanzando el límite superior. Este patrón es un claro indicio de que los retornos acumulados no tienen independencia temporal; cada valor está fuertemente correlacionado con todos los valores anteriores. Esto es consistente con el hecho de que los retornos acumulados siguen una tendencia en el tiempo, lo que hace que se mantengan altamente correlacionados y que el efecto acumulativo de los retornos sea persistente. En ambos casos, estos gráficos resaltan la naturaleza no aleatoria y la fuerte dependencia temporal de las series de volatilidad y retornos acumulados del Bitcoin.
Pruebas Ljunj box y Dickey Fuller para At y las volatilidades#
from statsmodels.tsa.stattools import adfuller
from statsmodels.stats.diagnostic import acorr_ljungbox
import pandas as pd
# Función para realizar las pruebas Ljung-Box y Dickey-Fuller y retornar los resultados en un diccionario
def pruebas_ljung_box_dickey_fuller(serie, nombre_serie):
resultados = {}
# Eliminar valores nulos
serie = serie.dropna()
if len(serie) > 0:
# Prueba Dickey-Fuller aumentada
adf_resultado = adfuller(serie)
resultados['ADF_stat'] = adf_resultado[0]
resultados['ADF_p_val'] = adf_resultado[1]
# Prueba Ljung-Box
ljung_box_resultado = acorr_ljungbox(serie, lags=[10], return_df=True)
resultados['LjungBox_stat'] = ljung_box_resultado['lb_stat'].values[0]
resultados['LjungBox_p_val'] = ljung_box_resultado['lb_pvalue'].values[0]
else:
# Si la serie no tiene suficientes datos
resultados['ADF_stat'] = None
resultados['ADF_p_val'] = None
resultados['LjungBox_stat'] = None
resultados['LjungBox_p_val'] = None
return resultados
# Series de interés
series = ['Volatilidad_7', 'Volatilidad_14', 'Volatilidad_21', 'Volatilidad_28', 'At']
resultados_pruebas = []
# Realizar las pruebas para cada serie y almacenar los resultados
for serie in series:
resultados = pruebas_ljung_box_dickey_fuller(df[serie], serie)
resultados['Serie'] = serie # Añadir el nombre de la serie
resultados_pruebas.append(resultados)
# Crear un DataFrame con los resultados
df_resultados = pd.DataFrame(resultados_pruebas)
# Reorganizar las columnas para mayor claridad
df_resultados = df_resultados[['Serie', 'ADF_stat', 'ADF_p_val', 'LjungBox_stat', 'LjungBox_p_val']]
# Mostrar la tabla con todos los resultados
print(df_resultados)
Serie ADF_stat ADF_p_val LjungBox_stat LjungBox_p_val
0 Volatilidad_7 -7.461204 5.348106e-11 20281.088500 0.0
1 Volatilidad_14 -6.800477 2.243763e-09 33518.917481 0.0
2 Volatilidad_21 -6.739235 3.149657e-09 39480.766093 0.0
3 Volatilidad_28 -5.342915 4.459348e-06 42274.381698 0.0
4 At -1.617701 4.739347e-01 48959.539109 0.0
Analisis de los resultados de pruebas de estacionaridad#
Los resultados de las pruebas de ADF y Ljung-Box que he analizado me brindan información crucial sobre la naturaleza de las series de tiempo que estoy evaluando. En primer lugar, al revisar la prueba ADF, puedo concluir que las series Volatilidad_7, Volatilidad_14, Volatilidad_21 y Volatilidad_28 son estacionarias, ya que sus estadísticos son suficientemente negativos y los p-valores son muy bajos, lo que indica que puedo rechazar la hipótesis nula de que contienen una raíz unitaria.
Sin embargo, la serie At no es estacionaria. Su estadístico ADF es menos negativo y su p-valor es superior a 0.05, lo que significa que no puedo rechazar la hipótesis nula en este caso. Esto sugiere que podría ser necesario aplicar alguna transformación o diferenciar la serie para lograr la estacionariedad, lo cual es fundamental para el análisis de series de tiempo.
Por otro lado, al evaluar los resultados de la prueba de Ljung-Box, encuentro que todas las series, incluso las que son estacionarias, presentan evidencia de autocorrelación en los residuos, ya que los p-valores son prácticamente cero. Esto indica que hay patrones en los datos que no han sido capturados por el análisis hasta ahora. La presencia de autocorrelación sugiere que podría ser útil investigar más a fondo estos datos y considerar ajustes adicionales en mi enfoque para lograr una mejor comprensión de su comportamiento.
A pesar de que las series de volatilidad parecen ser estacionarias según las pruebas ADF, la evidencia de autocorrelación observada en las pruebas ACF me lleva a la decisión de diferenciarlas. En resumen, tengo avances significativos con las series estacionarias, pero la autocorrelación en los residuos me indica que es necesario profundizar en el análisis y ajustar mi enfoque para obtener resultados más precisos y relevantes. Además, debo abordar el tratamiento de la serie At para integrarla adecuadamente en mi evaluación.
Como At no nos dio estacionario, lo diferenciamos y repetimos analisis#
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.stattools import acf
from statsmodels.tsa.seasonal import seasonal_decompose
import statsmodels.api as sm
# Aplicar una diferencia a la serie At
df['At_diff'] = df['At'].diff().dropna()
# Graficar la ACF de la serie diferenciada
fig, ax = plt.subplots(figsize=(10, 6))
sm.graphics.tsa.plot_acf(df['At_diff'].dropna(), ax=ax)
plt.title('ACF de At Diferenciada')
plt.show()
# Descomposición de la serie diferenciada
result = seasonal_decompose(df['At_diff'].dropna(), model='additive', period=30)
# Graficar la descomposición
result.plot()
plt.show()
import matplotlib.pyplot as plt
from statsmodels.tsa.seasonal import seasonal_decompose
import statsmodels.api as sm
# Lista de las volatilidades a diferenciar
volatilidades = ['Volatilidad_7', 'Volatilidad_14', 'Volatilidad_21', 'Volatilidad_28']
# Bucle para diferenciar, graficar ACF y descomposición de cada volatilidad
for vol in volatilidades:
# Aplicar la diferenciación
df[f'{vol}_diff'] = df[vol].diff().dropna()
# Graficar la ACF de la volatilidad diferenciada
fig, ax = plt.subplots(figsize=(10, 6))
sm.graphics.tsa.plot_acf(df[f'{vol}_diff'].dropna(), ax=ax)
plt.title(f'ACF de {vol} Diferenciada')
plt.show()
# Descomposición de la volatilidad diferenciada
result = seasonal_decompose(df[f'{vol}_diff'].dropna(), model='additive', period=30)
# Graficar la descomposición
result.plot()
plt.suptitle(f'Descomposición de {vol} Diferenciada', y=1.02)
plt.show()
Decidimos aplicar diferenciación a las series de volatilidad a pesar de que los resultados de la prueba de Dickey-Fuller indicaban que eran estacionarias. La razón detrás de esta decisión fue que la función de autocorrelación (ACF) no mostraba un comportamiento claramente estacionario, lo que nos llevó a cuestionar la estacionariedad de las series.
Aunque el test de Dickey-Fuller sugiere que las series cumplen con los criterios de estacionariedad, la ACF reveló patrones que no eran consistentes con esta conclusión. La ACF puede mostrar la falta de una disminución rápida de la autocorrelación, lo que sugiere que la serie podría tener una estructura más compleja o contener elementos no observados.
Por lo tanto, optamos por diferenciar las series para asegurarnos de que eliminábamos cualquier tendencia o comportamiento no estacionario que pudiera no haber sido captado completamente por la prueba de Dickey-Fuller. Esta decisión nos permite obtener series más adecuadas para análisis posteriores y modelado, asegurando que nuestras inferencias sean más robustas y confiables.
Agregación por grupos (semanas, días, meses) y Cálculo de estadísticas móviles para At y Volatilidades#
import pandas as pd
# Columnas de interés (At y volatilidades)
variables = ['At', 'Volatilidad_7', 'Volatilidad_14', 'Volatilidad_21', 'Volatilidad_28']
# Agregar por semana
weekly_data = df.resample('W', on='Date').agg({var: ['mean', 'std'] for var in variables})
# Agregar por mes
monthly_data = df.resample('M', on='Date').agg({var: ['mean', 'std'] for var in variables})
# Agregar por día
daily_data = df.resample('D', on='Date').agg({var: ['mean', 'std'] for var in variables})
# Mostrar el resultado
print("Datos semanales (media y desviación estándar):")
print(weekly_data.head())
print("\nDatos mensuales (media y desviación estándar):")
print(monthly_data.head())
print("\nDatos diarios (media y desviación estándar):")
print(daily_data.head())
Datos semanales (media y desviación estándar):
At Volatilidad_7 Volatilidad_14 \
mean std mean std mean std
Date
2010-07-18 -4.322978 NaN 0.0 NaN 0.0 NaN
2010-07-25 -4.322978 0.0 0.0 0.0 0.0 0.0
2010-08-01 -4.322978 0.0 0.0 0.0 0.0 0.0
2010-08-08 -4.322978 0.0 0.0 0.0 0.0 0.0
2010-08-15 -4.322978 0.0 0.0 0.0 0.0 0.0
Volatilidad_21 Volatilidad_28
mean std mean std
Date
2010-07-18 0.0 NaN 0.0 NaN
2010-07-25 0.0 0.0 0.0 0.0
2010-08-01 0.0 0.0 0.0 0.0
2010-08-08 0.0 0.0 0.0 0.0
2010-08-15 0.0 0.0 0.0 0.0
Datos mensuales (media y desviación estándar):
At Volatilidad_7 Volatilidad_14 \
mean std mean std mean
Date
2010-07-31 -4.322978 0.000000 0.000000 0.000000 0.000000
2010-08-31 -4.322978 0.000000 0.000000 0.000000 0.000000
2010-09-30 -4.322978 0.000000 0.000000 0.000000 0.000000
2010-10-31 -4.226204 0.200805 0.058310 0.087039 0.108616
2010-11-30 -3.903534 0.242200 0.208736 0.097184 0.204451
Volatilidad_21 Volatilidad_28
std mean std mean std
Date
2010-07-31 0.000000 0.000000 0.000000 0.000000 0.000000
2010-08-31 0.000000 0.000000 0.000000 0.000000 0.000000
2010-09-30 0.000000 0.000000 0.000000 0.009449 0.028832
2010-10-31 0.087996 0.143952 0.072847 0.163912 0.045570
2010-11-30 0.033481 0.198878 0.020037 0.207881 0.025490
Datos diarios (media y desviación estándar):
At Volatilidad_7 Volatilidad_14 Volatilidad_21 \
mean std mean std mean std mean
Date
2010-07-18 -4.322978 NaN 0.0 NaN 0.0 NaN 0.0
2010-07-19 -4.322978 NaN 0.0 NaN 0.0 NaN 0.0
2010-07-20 -4.322978 NaN 0.0 NaN 0.0 NaN 0.0
2010-07-21 -4.322978 NaN 0.0 NaN 0.0 NaN 0.0
2010-07-22 -4.322978 NaN 0.0 NaN 0.0 NaN 0.0
Volatilidad_28
std mean std
Date
2010-07-18 NaN 0.0 NaN
2010-07-19 NaN 0.0 NaN
2010-07-20 NaN 0.0 NaN
2010-07-21 NaN 0.0 NaN
2010-07-22 NaN 0.0 NaN
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_10416\1952300390.py:12: FutureWarning:
'M' is deprecated and will be removed in a future version, please use 'ME' instead.
Media y desviacion standard movil para AT y las volatilidades ( Semanal, Mensual y Anual)#
import matplotlib.pyplot as plt
# Lista de variables (At y volatilidades)
variables = ['At', 'Volatilidad_7', 'Volatilidad_14', 'Volatilidad_21', 'Volatilidad_28']
colors = ['blue', 'green', 'orange', 'red', 'purple']
# Graficar el comportamiento de la media y desviación estándar por semanas para cada variable
for i, var in enumerate(variables):
plt.figure(figsize=(10, 6))
plt.plot(weekly_data.index, weekly_data[var]['mean'], label=f'Media Semanal - {var}', color=colors[i])
plt.fill_between(weekly_data.index, weekly_data[var]['mean'] - weekly_data[var]['std'],
weekly_data[var]['mean'] + weekly_data[var]['std'], color=colors[i], alpha=0.2)
plt.title(f'Comportamiento de la Media y Desviación Estándar Semanal - {var}')
plt.xlabel('Fecha')
plt.ylabel(var)
plt.legend()
plt.xticks(rotation=45)
plt.show()
# Graficar el comportamiento de la media y desviación estándar mensual para cada variable
for i, var in enumerate(variables):
plt.figure(figsize=(10, 6))
plt.plot(monthly_data.index, monthly_data[var]['mean'], label=f'Media Mensual - {var}', color=colors[i])
plt.fill_between(monthly_data.index, monthly_data[var]['mean'] - monthly_data[var]['std'],
monthly_data[var]['mean'] + monthly_data[var]['std'], color=colors[i], alpha=0.2)
plt.title(f'Comportamiento de la Media y Desviación Estándar Mensual - {var}')
plt.xlabel('Fecha')
plt.ylabel(var)
plt.legend()
plt.xticks(rotation=45)
plt.show()
# Agregar por año para cada variable
annual_data = df.resample('Y', on='Date').agg({var: ['mean', 'std'] for var in variables})
# Graficar el comportamiento de la media y desviación estándar anual para cada variable
for i, var in enumerate(variables):
plt.figure(figsize=(10, 6))
plt.plot(annual_data.index, annual_data[var]['mean'], label=f'Media Anual - {var}', color=colors[i])
plt.fill_between(annual_data.index, annual_data[var]['mean'] - annual_data[var]['std'],
annual_data[var]['mean'] + annual_data[var]['std'], color=colors[i], alpha=0.2)
plt.title(f'Comportamiento de la Media y Desviación Estándar Anual - {var}')
plt.xlabel('Fecha')
plt.ylabel(var)
plt.legend()
plt.xticks(rotation=45)
plt.show()
import matplotlib.pyplot as plt
# Lista de variables (At y volatilidades)
variables = ['At', 'Volatilidad_7', 'Volatilidad_14', 'Volatilidad_21', 'Volatilidad_28']
colors = ['blue', 'green', 'orange', 'red', 'purple']
# Calcular la media móvil y la desviación estándar móvil con una ventana de 30 días para cada variable
for var in variables:
df[f'Mean_Mobile_{var}'] = df[var].rolling(window=30).mean()
df[f'Std_Mobile_{var}'] = df[var].rolling(window=30).std()
# Graficar la media móvil y la desviación estándar móvil para cada variable
for i, var in enumerate(variables):
plt.figure(figsize=(10, 6))
plt.plot(df['Date'], df[f'Mean_Mobile_{var}'], label=f'Media Móvil (30 días) - {var}', color=colors[i])
plt.fill_between(df['Date'],
df[f'Mean_Mobile_{var}'] - df[f'Std_Mobile_{var}'],
df[f'Mean_Mobile_{var}'] + df[f'Std_Mobile_{var}'],
color=colors[i], alpha=0.2)
plt.title(f'Media Móvil y Desviación Estándar Móvil (30 días) - {var}')
plt.xlabel('Fecha')
plt.ylabel(var)
plt.legend()
plt.xticks(rotation=45)
plt.show()
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_10416\1982343715.py:34: FutureWarning:
'Y' is deprecated and will be removed in a future version, please use 'YE' instead.
Analisis de las medias y desviacion standard del bitcoin#
Al observar los gráficos de la desviación estándar y la media de los retornos acumulados y las volatilidades del Bitcoin en horizontes de 7, 14, 21 y 28 días durante los últimos 14 años, me encuentro con patrones interesantes que reflejan la naturaleza volátil y dinámica del mercado de criptomonedas.
El gráfico de la desviación estándar muestra picos significativos en períodos de alta volatilidad, lo que coincide con eventos clave en la historia del Bitcoin, como la adopción institucional, las regulaciones gubernamentales o cambios en la percepción pública. En particular, puedo notar que los horizontes de 7 días tienen desviaciones estándar más altas, indicando que los movimientos a corto plazo son mucho más erráticos y sensibles a cambios repentinos en el mercado. Por otro lado, las desviaciones estándar de 14, 21 y 28 días tienden a ser más suaves, lo que sugiere que la volatilidad se estabiliza un poco a medida que se amplía el horizonte de tiempo.
Al analizar el gráfico de la media de los retornos acumulados, se observa una tendencia creciente en general, lo que indica que, a largo plazo, Bitcoin ha mostrado un desempeño positivo. Sin embargo, también se aprecian algunas caídas significativas en ciertos periodos, como en 2018, cuando el mercado experimentó una corrección importante. Los horizontes de 7 días muestran fluctuaciones más pronunciadas en la media de los retornos, lo que es indicativo de los altibajos típicos del trading en el corto plazo. A medida que el horizonte se amplía a 14, 21 y 28 días, la media de los retornos se vuelve más estable, reflejando una tendencia más clara hacia la recuperación y el crecimiento sostenido.
Comparando ambos gráficos, puedo concluir que existe una relación inversa entre la media de los retornos y la desviación estándar en ciertos momentos. Durante períodos de alta volatilidad, los retornos tienden a ser más inestables, mientras que en períodos de menor volatilidad, los retornos acumulados tienden a ser más positivos y constantes.
Esta visualización me permite reflexionar sobre la importancia de considerar tanto la media como la desviación estándar en la evaluación del rendimiento del Bitcoin. Estos gráficos no solo ilustran la naturaleza riesgosa de invertir en criptomonedas, sino que también resaltan las oportunidades que pueden surgir en medio de la volatilidad, haciendo que la toma de decisiones estratégicas sea esencial para navegar en este entorno de inversión.
Distribucion de frecuencias para diferentes lags#
import seaborn as sns
# Lista de variables (At y volatilidades)
variables = ['At', 'Volatilidad_7', 'Volatilidad_14', 'Volatilidad_21', 'Volatilidad_28']
colors = ['blue', 'green', 'orange', 'red', 'purple']
# Calcular diferencias con diferentes lags (1 día, 7 días, 30 días) para cada variable
for var in variables:
df[f'{var}_diff_1'] = df[var].diff(1) # Lag de 1 día
df[f'{var}_diff_7'] = df[var].diff(7) # Lag de 1 semana
df[f'{var}_diff_30'] = df[var].diff(30) # Lag de 1 mes
# Graficar la distribución de las diferencias para diferentes lags para cada variable
for i, var in enumerate(variables):
plt.figure(figsize=(10, 6))
sns.histplot(df[f'{var}_diff_1'].dropna(), label=f'Lag 1 día - {var}', color=colors[i], kde=True)
sns.histplot(df[f'{var}_diff_7'].dropna(), label=f'Lag 7 días - {var}', color=colors[i], linestyle='--', kde=True)
sns.histplot(df[f'{var}_diff_30'].dropna(), label=f'Lag 30 días - {var}', color=colors[i], linestyle=':', kde=True)
plt.title(f'Distribución de Frecuencias para Diferentes Lags - {var}')
plt.xlabel(f'Diferencias de {var}')
plt.ylabel('Frecuencia')
plt.legend()
plt.show()
Analisis de la distribucion de lags para volatilidad y retornos acumulados del bitcoin#
Al examinar las distribuciones de frecuencia de las cuatro volatilidades (Volatilidad_7, Volatilidad_14, Volatilidad_21 y Volatilidad_28) y los retornos acumulados para diferentes lags, noto patrones interesantes que aportan más claridad sobre el comportamiento de estos datos.
En primer lugar, al observar las distribuciones de frecuencia de las volatilidades, todas parecen seguir una distribución normal y presentan formas similares. Esto es alentador, ya que sugiere que las volatilidades se comportan de manera predecible y pueden ser modeladas con técnicas estadísticas que asumen normalidad. Las colas de estas distribuciones son relativamente simétricas y no muestran sesgo significativo, lo que indica una estabilidad en las fluctuaciones de los precios del Bitcoin a lo largo de los horizontes analizados.
Sin embargo, cuando me dirijo a los retornos acumulados, la situación es algo diferente. Al visualizar su distribución de frecuencia, puedo observar que es un poco más ancha en comparación con las de volatilidad. Esto sugiere que los retornos acumulados son más propensos a mostrar extremos, lo que puede indicar que hay una mayor variabilidad en los cambios de precio a lo largo del tiempo. Este ensanchamiento puede ser el resultado de eventos de mercado inusuales o períodos de alta actividad especulativa, que son comunes en el mercado de criptomonedas.
Además, la forma de la distribución de los retornos acumulados no es tan simétrica como la de las volatilidades, lo que puede indicar la presencia de colas más pesadas. Esto significa que, aunque la mayoría de los retornos pueden agruparse alrededor de la media, hay una probabilidad mayor de observar movimientos extremos, ya sean positivos o negativos. Este comportamiento resalta el riesgo asociado con la inversión en Bitcoin, ya que aunque la tendencia general pueda ser positiva, también hay momentos de caída brusca que pueden impactar significativamente el capital invertido.
Medias moviles, retiro de la tendencia y el Ljung box y Dickey fuller test sin tendencia#
import matplotlib.pyplot as plt
# Lista de variables (At y volatilidades)
variables = ['At', 'Volatilidad_7', 'Volatilidad_14', 'Volatilidad_21', 'Volatilidad_28']
colors = ['blue', 'green', 'orange', 'red', 'purple']
# Calcular medias móviles con diferentes ventanas (2, 3, 4 días) para cada variable
for var in variables:
df[f'M2_{var}'] = df[var].rolling(window=2).mean()
df[f'M3_{var}'] = df[var].rolling(window=3).mean()
df[f'M4_{var}'] = df[var].rolling(window=4).mean()
# Graficar las medias móviles junto con la serie original para cada variable
for i, var in enumerate(variables):
plt.figure(figsize=(10, 6))
plt.plot(df['Date'], df[var], label=f'{var} Original', color='black')
plt.plot(df['Date'], df[f'M2_{var}'], label=f'Media Móvil (2 días) - {var}', color='blue')
plt.plot(df['Date'], df[f'M3_{var}'], label=f'Media Móvil (3 días) - {var}', color='red')
plt.plot(df['Date'], df[f'M4_{var}'], label=f'Media Móvil (4 días) - {var}', color='green')
plt.title(f'Medias Móviles de Diferentes Ventanas - {var}')
plt.xlabel('Fecha')
plt.ylabel(var)
plt.legend()
plt.xticks(rotation=45)
plt.show()
# Remover la tendencia restando la media móvil de 4 días de la serie original para cada variable
for i, var in enumerate(variables):
df[f'Detrended_{var}'] = df[var] - df[f'M4_{var}']
# Graficar la serie sin tendencia para cada variable
for i, var in enumerate(variables):
plt.figure(figsize=(10, 6))
plt.plot(df['Date'], df[f'Detrended_{var}'], label=f'Serie sin Tendencia - {var}', color='purple')
plt.title(f'Serie sin Tendencia (Media Móvil 4 días) - {var}')
plt.xlabel('Fecha')
plt.ylabel(f'{var} Detrended')
plt.legend()
plt.xticks(rotation=45)
plt.show()
Analisis de medias movil y serie sin tendencia para volatilidad y retornos acumulados#
Al analizar las medias móviles y las series de las volatilidades y los retornos acumulados después de retirarles la tendencia, obtengo una perspectiva más clara sobre el comportamiento subyacente de estas variables a lo largo del tiempo.
Observando las medias móviles de las volatilidades (Volatilidad_7, Volatilidad_14, Volatilidad_21 y Volatilidad_28), noto que estas líneas suavizan las fluctuaciones diarias, permitiéndome identificar patrones con mayor claridad. Las medias móviles muestran una tendencia a la baja en ciertos períodos, lo que indica que la volatilidad del Bitcoin ha disminuido, posiblemente debido a una consolidación del mercado o una menor incertidumbre en el entorno económico. Sin embargo, también se observan picos de volatilidad que correlacionan con eventos de alta actividad en el mercado, como anuncios importantes o cambios regulatorios.
En cuanto a los retornos acumulados, la media móvil también proporciona una perspectiva sobre la tendencia general. Al suavizar los datos, puedo identificar períodos de crecimiento sostenido y momentos de caída. La media móvil revela que, a pesar de la naturaleza volátil de los retornos diarios, hay fases en las que los retornos acumulados tienden a ser positivos y estables, lo que sugiere que, a largo plazo, Bitcoin ha tenido un desempeño sólido.
Cuando analizo las series de volatilidades y retornos acumulados después de retirar la tendencia, me doy cuenta de que ahora puedo centrarme en las fluctuaciones alrededor de la media. Este ajuste me permite observar más claramente los cambios estacionales o cíclicos que podrían haber estado ocultos por la tendencia general. Para las volatilidades, el gráfico muestra que hay oscilaciones regulares, sugiriendo que, a pesar de que la tendencia general podría ser estable, las volatilidades experimentan fluctuaciones significativas en respuesta a cambios en el mercado. Estas variaciones resaltan la naturaleza inherente de los activos volátiles como el Bitcoin.
En el caso de los retornos acumulados, tras retirar la tendencia, la serie muestra una variabilidad más notable, indicando que los retornos pueden tener patrones de comportamiento que no son evidentes al observar la serie original. Este ajuste revela momentos de aumento y disminución abrupta, lo que refuerza la idea de que el mercado puede experimentar episodios de extrema actividad o inactividad, independientemente de la tendencia general.
from statsmodels.tsa.stattools import adfuller
from statsmodels.stats.diagnostic import acorr_ljungbox
# Lista de variables (At y volatilidades)
variables = ['At', 'Volatilidad_7', 'Volatilidad_14', 'Volatilidad_21', 'Volatilidad_28']
# Aplicar la prueba de Dickey-Fuller y Ljung-Box para cada variable sin tendencia
for var in variables:
print(f'\nResultados para {var}:')
# Prueba de Dickey-Fuller en la serie sin tendencia
adf_result = adfuller(df[f'Detrended_{var}'].dropna())
print(f'Estadístico ADF: {adf_result[0]}')
print(f'Valor P: {adf_result[1]}')
# Prueba de Ljung-Box en la serie sin tendencia
ljungbox_result = acorr_ljungbox(df[f'Detrended_{var}'].dropna(), lags=[10], return_df=True)
print(ljungbox_result)
Resultados para At:
Estadístico ADF: -10.277894284748266
Valor P: 3.847531979753958e-18
lb_stat lb_pvalue
10 1532.556764 0.0
Resultados para Volatilidad_7:
Estadístico ADF: -18.787625910977344
Valor P: 2.0237103913257434e-30
lb_stat lb_pvalue
10 4448.710266 0.0
Resultados para Volatilidad_14:
Estadístico ADF: -17.568596257060353
Valor P: 4.062321036340978e-30
lb_stat lb_pvalue
10 3833.089277 0.0
Resultados para Volatilidad_21:
Estadístico ADF: -16.209875902476455
Valor P: 3.996632530040086e-29
lb_stat lb_pvalue
10 4017.864785 0.0
Resultados para Volatilidad_28:
Estadístico ADF: -16.39354305552273
Valor P: 2.679865846336868e-29
lb_stat lb_pvalue
10 4053.593697 0.0
Analisis de los resultados#
Al analizar los resultados de las pruebas ADF y Ljung-Box para las variables sin tendencia, obtengo información crucial sobre la estacionariedad y la autocorrelación de estas series.
La serie At presenta un estadístico ADF de -10.28 y un valor P de 3.85e-18, lo que indica que es estacionaria, ya que puedo rechazar la hipótesis nula de que contiene una raíz unitaria. Esto sugiere que la serie se mantiene en torno a una media constante.
En cuanto a las volatilidades, Volatilidad_7 tiene un estadístico ADF de -18.79 y un valor P de 2.02e-30; Volatilidad_14 muestra un estadístico de -17.57 con un valor P de 4.06e-30; Volatilidad_21 tiene un estadístico de -16.21 y un valor P de 3.99e-29; y Volatilidad_28 presenta un estadístico de -16.39 con un valor P de 2.68e-29. Todos estos resultados confirman que las series de volatilidad son también estacionarias.
Sin embargo, los resultados de la prueba de Ljung-Box, que indican estadísticos muy altos y valores P de 0.0 para todas las series, revelan una fuerte presencia de autocorrelación en los residuos. Esto sugiere que, a pesar de la estacionariedad, existen patrones en los datos que no han sido completamente capturados.
En resumen, aunque todas las series son estacionarias, la autocorrelación en los residuos sugiere que debo explorar modelos que puedan capturar estas relaciones temporales, como ARIMA o modelos de suavizado exponencial, para lograr una comprensión más completa de las dinámicas de las series.
Descomposicion para medias moviles de At y volatilidades, luego revisamos Ljung box#
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.tsa.seasonal import seasonal_decompose
from statsmodels.stats.diagnostic import acorr_ljungbox
# Lista de variables (At y volatilidades)
variables = ['At', 'Volatilidad_7', 'Volatilidad_14', 'Volatilidad_21', 'Volatilidad_28']
# Aplicar media móvil de 7 días y realizar la descomposición para cada variable
for var in variables:
print(f'\nResultados para {var}:')
# Aplicar media móvil de 7 días para suavizar
df[f'{var}_MA7'] = df[var].rolling(window=7).mean()
# Graficar la serie original y la suavizada
plt.figure(figsize=(10, 6))
plt.plot(df['Date'], df[var], label=f'{var} Original', color='black')
plt.plot(df['Date'], df[f'{var}_MA7'], label=f'Media Móvil 7 días - {var}', color='blue')
plt.title(f'{var} Original vs. Media Móvil (7 días)')
plt.xlabel('Fecha')
plt.ylabel(var)
plt.legend()
plt.xticks(rotation=45)
plt.show()
# Descomposición de la serie temporal
decomposed = seasonal_decompose(df[f'{var}_MA7'].dropna(), model='additive', period=30)
# Graficar la descomposición
decomposed.plot()
plt.suptitle(f'Descomposición Aditiva - {var}', y=1.02)
plt.show()
# Obtener los residuos de la descomposición
residuos = decomposed.resid.dropna()
# Aplicar la prueba de Ljung-Box en los residuos
ljungbox_result = acorr_ljungbox(residuos, lags=[10], return_df=True)
# Mostrar los resultados de la prueba de Ljung-Box
print(ljungbox_result)
Resultados para At:
lb_stat lb_pvalue
10 14406.772058 0.0
Resultados para Volatilidad_7:
lb_stat lb_pvalue
10 15197.793646 0.0
Resultados para Volatilidad_14:
lb_stat lb_pvalue
10 16519.537761 0.0
Resultados para Volatilidad_21:
lb_stat lb_pvalue
10 17942.086344 0.0
Resultados para Volatilidad_28:
lb_stat lb_pvalue
10 16302.266458 0.0
Analisis de las medias moviles y volatilidades para el Bitocin#
Al realizar la descomposición de medias móviles para cada variable, obtengo una visión más clara de las tendencias, la estacionalidad y los componentes aleatorios de las series.
Al observar la descomposición de medias móviles de la serie At, identifico que la tendencia es creciente, lo que indica un aumento sostenido en los valores a lo largo del tiempo. A pesar de esta tendencia clara, siempre encontramos picos que reflejan eventos de alta volatilidad o cambios abruptos en el mercado. Esto sugiere que, aunque la serie se comporta de manera predecible en general, aún es sensible a perturbaciones externas.
En la descomposición de Volatilidad_7, se nota una tendencia a la baja en ciertos períodos, pero también se presentan picos de volatilidad, especialmente durante eventos de alta incertidumbre en el mercado. Estos picos resaltan la naturaleza intrínsecamente volátil de esta variable, sugiriendo que, aunque hay períodos de estabilidad, las fluctuaciones pueden ser significativas.
La serie Volatilidad_14 muestra una tendencia similar a la de Volatilidad_7, con una clara reducción de la volatilidad en algunos tramos y picos correspondientes a momentos de alta actividad. Esta descomposición revela que, aunque hay períodos de estabilidad, las fluctuaciones pueden ser pronunciadas en respuesta a eventos específicos del mercado.
Al analizar Volatilidad_21, la tendencia también es descendente, pero con más variaciones que las series anteriores. La descomposición muestra una respuesta más rápida a los cambios en el mercado, evidenciando que la serie podría ser más sensible a eventos externos, reflejando picos que indican respuestas rápidas a situaciones de volatilidad.
Finalmente, en la descomposición de Volatilidad_28, se puede observar una tendencia general menos pronunciada, lo que implica que esta serie podría estar capturando patrones más amplios y menos volátiles en el tiempo. Sin embargo, también presenta fluctuaciones que indican momentos de mayor incertidumbre, mostrando picos que reflejan reacciones a eventos macroeconómicos o cambios significativos en el entorno de las criptomonedas.
Modelos estadisticos#
Dividimos en conjunto de entrenamiento, validacion y prediccion#
import pandas as pd
import numpy as np
from statsmodels.graphics.tsaplots import plot_acf
import matplotlib.pyplot as plt
from statsmodels.api import qqplot
from statsmodels.tsa.stattools import acf
from statsmodels.stats.diagnostic import normal_ad
from scipy import stats
# Cargar el dataset
df = pd.read_csv(BHdata)
# Convertir la columna 'Date' a tipo datetime
df['Date'] = pd.to_datetime(df['Date'], format='%m/%d/%Y')
# Convertir las columnas 'Price', 'Open', 'High', 'Low' eliminando comas y convirtiendo a float
df['Price'] = pd.to_numeric(df['Price'].str.replace(',', ''), errors='coerce')
df['Open'] = pd.to_numeric(df['Open'].str.replace(',', ''), errors='coerce')
df['High'] = pd.to_numeric(df['High'].str.replace(',', ''), errors='coerce')
df['Low'] = pd.to_numeric(df['Low'].str.replace(',', ''), errors='coerce')
# Convertir la columna 'Vol.' eliminando comas, reemplazando 'K' por 'e3' y 'M' por 'e6', y luego convertir a numérico
df['Vol.'] = df['Vol.'].str.replace('K', 'e3').str.replace('M', 'e6').str.replace(',', '')
df['Vol.'] = pd.to_numeric(df['Vol.'], errors='coerce')
# Convertir la columna 'Change %' eliminando el símbolo '%' y convirtiendo a float
df['Change %'] = df['Change %'].str.replace('%', '').astype(float)
# Eliminar las filas con valores nulos en la columna 'Price'
df.dropna(subset=['Price'], inplace=True)
# Calcular el retorno diario Rt
df['Rt'] = (df['Price'] - df['Price'].shift(1)) / df['Price'].shift(1)
# Calcular el retorno acumulado diario At
df['At'] = df['Rt'].cumsum()
# Función para calcular la volatilidad usando ventanas móviles
def calcular_volatilidad(data, ventana):
"""
Esta función calcula la volatilidad (desviación estándar)
de los retornos diarios utilizando una ventana móvil.
"""
return data['Rt'].rolling(window=ventana).std()
# Calcular volatilidad para diferentes ventanas
ventanas = [7, 14, 21, 28]
for ventana in ventanas:
df[f'Volatilidad_{ventana}'] = calcular_volatilidad(df, ventana)
# Mostrar los primeros 10 registros con las nuevas columnas de volatilidad
print(df[['Date', 'At', 'Volatilidad_7', 'Volatilidad_14', 'Volatilidad_21', 'Volatilidad_28']].head(15))
# Dividir los datos en entrenamiento, validación y prueba (últimos 28 datos serán para validación)
train_size = len(df) - 28
test_size = 28
variables = ['Price', 'At', 'Volatilidad_7']
# Crear diccionarios para almacenar los conjuntos de entrenamiento y validación para cada variable
train_data = {}
validation_data = {}
for var in variables:
train_data[var] = df[var].iloc[:train_size].reset_index(drop=True) # Resetear los índices
validation_data[var] = df[var].iloc[-test_size:].reset_index(drop=True)
Date At Volatilidad_7 Volatilidad_14 Volatilidad_21 \
0 2024-03-24 NaN NaN NaN NaN
1 2024-03-23 -0.047225 NaN NaN NaN
2 2024-03-22 -0.051165 NaN NaN NaN
3 2024-03-21 -0.024226 NaN NaN NaN
4 2024-03-20 0.011652 NaN NaN NaN
5 2024-03-19 -0.073884 NaN NaN NaN
6 2024-03-18 0.015465 NaN NaN NaN
7 2024-03-17 0.027257 0.057142 NaN NaN
8 2024-03-16 -0.017734 0.056814 NaN NaN
9 2024-03-15 0.045797 0.060785 NaN NaN
10 2024-03-14 0.073492 0.060813 NaN NaN
11 2024-03-13 0.097009 0.060247 NaN NaN
12 2024-03-12 0.075165 0.046199 NaN NaN
13 2024-03-11 0.083964 0.035128 NaN NaN
14 2024-03-10 0.040492 0.040414 0.04756 NaN
Volatilidad_28
0 NaN
1 NaN
2 NaN
3 NaN
4 NaN
5 NaN
6 NaN
7 NaN
8 NaN
9 NaN
10 NaN
11 NaN
12 NaN
13 NaN
14 NaN
Simple Exponential Smoothing (Sin Librería)#
# Función para calcular la suavización exponencial simple (SES)
def SES(y, alpha):
"""
Realiza la Suavización Exponencial Simple (SES) para una serie temporal 'y' con un factor de suavización 'alpha'.
Retorna las predicciones suavizadas.
"""
y_pred = [y[0]] # Inicializamos la predicción con el primer valor de la serie
for t in range(1, len(y)):
y_hat = alpha * y[t-1] + (1 - alpha) * y_pred[t-1]
y_pred.append(y_hat)
return y_pred
# Parámetros
alphas = [0.5] # Factores de suavización que queremos probar
ventanas = [7, 14, 21, 28] # Ventanas móviles para cada predicción
variables = ['Price', 'Volatilidad_14', 'At']
# Almacenaremos los resultados en un diccionario
predicciones = {}
# Dividimos el conjunto de validación y entrenamiento
train_size = len(df) - 28
test_size = 28
# Para cada variable y cada ventana
for variable in variables:
predicciones[variable] = {}
# Tomamos el conjunto de entrenamiento para esa variable
train_series = df[variable].iloc[:train_size].reset_index(drop=True)
for ventana in ventanas:
# Inicializar diccionario para cada ventana
predicciones[variable][ventana] = {}
for alpha in alphas:
# Aplicar SES sobre la ventana de entrenamiento
y_train_pred = SES(train_series, alpha)
# Ahora realizamos predicciones para el conjunto de validación (últimos 28 datos)
val_series = df[variable].iloc[-test_size:].reset_index(drop=True)
val_predictions = []
# Usamos la última predicción de entrenamiento para empezar a predecir la validación
last_train_value = y_train_pred[-1]
for i in range(test_size):
# Predicción para el día siguiente usando SES
new_val_pred = alpha * val_series.iloc[i] + (1 - alpha) * last_train_value
val_predictions.append(new_val_pred)
# Actualizamos la última predicción para el siguiente paso
last_train_value = new_val_pred
# Guardar las predicciones para esta ventana y alpha
predicciones[variable][ventana][alpha] = val_predictions
# Imprimir ejemplo de predicciones para verificar
for variable in variables:
for ventana in ventanas:
print(f"Predicciones para {variable}, ventana de {ventana} días, alpha={alphas[0]}:")
print(predicciones[variable][ventana][alphas[0]][:10]) # Mostrar las primeras 10 predicciones
print("\n")
Predicciones para Price, ventana de 7 días, alpha=0.1:
[np.float64(0.10006349633511052), np.float64(0.10005714670159949), np.float64(0.10005143203143954), np.float64(0.10004628882829558), np.float64(0.10004165994546602), np.float64(0.10003749395091943), np.float64(0.1000337445558275), np.float64(0.10003037010024476), np.float64(0.1000273330902203), np.float64(0.10002459978119826)]
Predicciones para Price, ventana de 14 días, alpha=0.1:
[np.float64(0.10006349633511052), np.float64(0.10005714670159949), np.float64(0.10005143203143954), np.float64(0.10004628882829558), np.float64(0.10004165994546602), np.float64(0.10003749395091943), np.float64(0.1000337445558275), np.float64(0.10003037010024476), np.float64(0.1000273330902203), np.float64(0.10002459978119826)]
Predicciones para Price, ventana de 21 días, alpha=0.1:
[np.float64(0.10006349633511052), np.float64(0.10005714670159949), np.float64(0.10005143203143954), np.float64(0.10004628882829558), np.float64(0.10004165994546602), np.float64(0.10003749395091943), np.float64(0.1000337445558275), np.float64(0.10003037010024476), np.float64(0.1000273330902203), np.float64(0.10002459978119826)]
Predicciones para Price, ventana de 28 días, alpha=0.1:
[np.float64(0.10006349633511052), np.float64(0.10005714670159949), np.float64(0.10005143203143954), np.float64(0.10004628882829558), np.float64(0.10004165994546602), np.float64(0.10003749395091943), np.float64(0.1000337445558275), np.float64(0.10003037010024476), np.float64(0.1000273330902203), np.float64(0.10002459978119826)]
Predicciones para Volatilidad_14, ventana de 7 días, alpha=0.1:
[np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan)]
Predicciones para Volatilidad_14, ventana de 14 días, alpha=0.1:
[np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan)]
Predicciones para Volatilidad_14, ventana de 21 días, alpha=0.1:
[np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan)]
Predicciones para Volatilidad_14, ventana de 28 días, alpha=0.1:
[np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan)]
Predicciones para At, ventana de 7 días, alpha=0.1:
[np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan)]
Predicciones para At, ventana de 14 días, alpha=0.1:
[np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan)]
Predicciones para At, ventana de 21 días, alpha=0.1:
[np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan)]
Predicciones para At, ventana de 28 días, alpha=0.1:
[np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan), np.float64(nan)]
import numpy as np
import pandas as pd
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
# Función para calcular el MAPE
def MAPE(y_true, y_pred):
return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
# Función para realizar predicciones con Simple Exponential Smoothing
def ses_predict(serie, alpha, test_size):
ses_pred = [serie.iloc[0]] # Usar el primer valor de la serie
for t in range(1, len(serie)):
pred = alpha * serie.iloc[t] + (1 - alpha) * ses_pred[-1]
ses_pred.append(pred)
pred_val = []
for t in range(test_size):
pred = alpha * df[variable].iloc[len(serie) + t] + (1 - alpha) * ses_pred[-1]
pred_val.append(pred)
return pred_val
# Función para evaluar una variable
def evaluar_variable(variable):
ventanas = [7, 14, 21, 28]
alphas = [0.5]
train_size = len(df) - 28
test_size = 28
resultados = []
# Evaluar el SES para la variable
for ventana in ventanas:
for alpha in alphas:
# Serie de tiempo de entrenamiento sin NaNs
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
# Verificar si hay suficientes datos para el entrenamiento
if len(train_series) < 1:
print(f"Skipping variable '{variable}' for ventana {ventana} and alpha {alpha} due to insufficient training data.")
continue
# Obtener las predicciones para el conjunto de validación
pred_val = ses_predict(train_series, alpha, test_size)
# Conjunto de validación real (últimos 28 valores de la serie) sin NaNs
val_series = df[variable].iloc[-test_size:].dropna().reset_index(drop=True)
# Verificar si hay suficientes datos para la validación
if len(val_series) < test_size:
print(f"Skipping variable '{variable}' for ventana {ventana} and alpha {alpha} due to insufficient validation data.")
continue
# Calcular las métricas
mae = mean_absolute_error(val_series, pred_val)
mse = mean_squared_error(val_series, pred_val)
rmse = np.sqrt(mse)
mape = MAPE(val_series, pred_val)
r2 = r2_score(val_series, pred_val)
# Guardar los resultados en una lista
resultados.append({
'Variable': variable,
'Ventana': ventana,
'Alpha': alpha,
'MAE': mae,
'MSE': mse,
'RMSE': rmse,
'MAPE': mape,
'R2': r2
})
return pd.DataFrame(resultados)
# Evaluar cada variable y combinar los resultados
resultados_totales = pd.concat([evaluar_variable('At'),
evaluar_variable('Price'),
evaluar_variable('Volatilidad_14')],
ignore_index=True)
# Mostrar la tabla con los resultados totales
print(resultados_totales)
Variable Ventana Alpha MAE MSE RMSE \
0 At 7 0.5 8.881784e-16 7.888609e-31 8.881784e-16
1 At 14 0.5 8.881784e-16 7.888609e-31 8.881784e-16
2 At 21 0.5 8.881784e-16 7.888609e-31 8.881784e-16
3 At 28 0.5 8.881784e-16 7.888609e-31 8.881784e-16
4 Price 7 0.5 2.211489e+00 4.890684e+00 2.211489e+00
5 Price 14 0.5 2.211489e+00 4.890684e+00 2.211489e+00
6 Price 21 0.5 2.211489e+00 4.890684e+00 2.211489e+00
7 Price 28 0.5 2.211489e+00 4.890684e+00 2.211489e+00
8 Volatilidad_14 7 0.5 2.161489e+00 4.672035e+00 2.161489e+00
9 Volatilidad_14 14 0.5 2.161489e+00 4.672035e+00 2.161489e+00
10 Volatilidad_14 21 0.5 2.161489e+00 4.672035e+00 2.161489e+00
11 Volatilidad_14 28 0.5 2.161489e+00 4.672035e+00 2.161489e+00
MAPE R2
0 2.054552e-14 0.000000e+00
1 2.054552e-14 0.000000e+00
2 2.054552e-14 0.000000e+00
3 2.054552e-14 0.000000e+00
4 2.211489e+03 -2.539388e+34
5 2.211489e+03 -2.539388e+34
6 2.211489e+03 -2.539388e+34
7 2.211489e+03 -2.539388e+34
8 inf 0.000000e+00
9 inf 0.000000e+00
10 inf 0.000000e+00
11 inf 0.000000e+00
Variable |
Ventana |
Alpha |
MAE |
MSE |
RMSE |
MAPE |
R2 |
|---|---|---|---|---|---|---|---|
At |
7 |
0.5 |
8.881784e-16 |
7.888609e-31 |
8.881784e-16 |
2.054552e-14 |
0.000000e+00 |
Price |
7 |
0.5 |
2.211489e+00 |
4.890684e+00 |
2.211489e+00 |
2.211489e+03 |
-2.539388e+34 |
Volatilidad_14 |
7 |
0.5 |
2.161489e+00 |
4.672035e+00 |
2.161489e+00 |
inf |
0.000000e+00 |
Al analizar los resultados, se observa que se han calculado diferentes métricas de error (MAE, MSE, RMSE, MAPE y R²) para las variables At, Price y Volatilidad_14 utilizando un método de suavizado exponencial con un parámetro 𝛼 α de 0.5. Sin embargo, es importante destacar que, dado que el suavizado exponencial es una técnica que se utiliza para hacer pronósticos a partir de una serie temporal, el concepto de “ventana” como tal no es aplicable en este contexto.
En el suavizado exponencial, el valor actual se calcula en función de una combinación ponderada del valor observado más reciente y la estimación anterior, lo que permite dar más peso a los datos más recientes. Por esta razón, no se puede establecer un número de ventanas como en otros métodos de suavizado, como las medias móviles, donde se especifica un tamaño de ventana para el cálculo de promedios.
En los resultados, para las variables At y Volatilidad_14, los errores de predicción son extremadamente bajos (en el orden de 1 0 − 16 10 −16 y 1 0 − 30 10 −30 ), lo que sugiere que el modelo se ajusta muy bien a los datos, mientras que para Price, los errores son significativamente mayores, indicando que puede haber más variabilidad en la serie o que el modelo no captura adecuadamente la dinámica de esta variable.
Además, el hecho de que el MAPE sea infinito para las series de volatilidad indica que hay valores en los que el error porcentual no puede calcularse adecuadamente, lo cual puede ser un indicativo de que los datos de entrada tienen características que dificultan el pronóstico preciso.
En resumen, la naturaleza del suavizado exponencial significa que no se puede aplicar el concepto de ventanas de la misma manera que en otros métodos, y los resultados reflejan el rendimiento del modelo en diferentes variables, mostrando un ajuste notablemente bueno para At y Volatilidad_14, mientras que Price presenta un desafío mayor.
Residuos del entrenamiento#
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from statsmodels.stats.diagnostic import acorr_ljungbox
from statsmodels.stats.stattools import jarque_bera
# Función para calcular el MAPE
def MAPE(y_true, y_pred):
return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
# Función para realizar predicciones con Simple Exponential Smoothing
def ses_predict(serie, alpha, test_size):
ses_pred = [serie.iloc[0]] # Usar el primer valor de la serie
for t in range(1, len(serie)):
pred = alpha * serie.iloc[t] + (1 - alpha) * ses_pred[-1]
ses_pred.append(pred)
pred_val = []
for t in range(test_size):
pred = alpha * serie.iloc[-1] + (1 - alpha) * ses_pred[-1] # Cambiado para usar el último valor de la serie
pred_val.append(pred)
return pred_val
# Función para evaluar la variable y analizar residuos
def evaluar_variable_con_residuos(variable):
ventanas = [7, 14, 21, 28]
alphas = [0.5]
train_size = len(df) - 28
test_size = 28
resultados = []
for ventana in ventanas:
for alpha in alphas:
# Serie de tiempo de entrenamiento sin NaNs
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
# Verificar que hay suficientes datos para entrenar
if len(train_series) < 1:
print(f"Skipping variable '{variable}' for ventana {ventana} and alpha {alpha} due to insufficient training data.")
continue
# Obtener las predicciones para el conjunto de validación
pred_val = ses_predict(train_series, alpha, test_size)
# Conjunto de validación real (últimos 28 valores de la serie) sin NaNs
val_series = df[variable].iloc[-test_size:].dropna().reset_index(drop=True)
# Calcular los residuos
residuos = train_series - train_series.mean()
# Calcular métricas
mae = mean_absolute_error(val_series, pred_val)
mse = mean_squared_error(val_series, pred_val)
rmse = np.sqrt(mse)
mape = MAPE(val_series, pred_val)
r2 = r2_score(val_series, pred_val)
# Pruebas de hipótesis
ljung_box_results = acorr_ljungbox(residuos, lags=[10], return_df=True)
ljung_box_pvalue = ljung_box_results['lb_pvalue'].values[0] if not ljung_box_results.empty else np.nan
jarque_bera_results = jarque_bera(residuos)
jarque_bera_stat = jarque_bera_results[0]
jarque_bera_pvalue = jarque_bera_results[1]
# Guardar resultados
resultados.append({
'Variable': variable,
'Ventana': ventana,
'MAPE': mape,
'MAE': mae,
'RMSE': rmse,
'MSE': mse,
'R2': r2,
'Ljung-Box (p-value)': ljung_box_pvalue,
'Jarque-Bera (p-value)': jarque_bera_pvalue
})
# Gráficos
plt.figure(figsize=(15, 5))
# Serie de residuos
plt.subplot(1, 3, 1)
plt.plot(residuos)
plt.title(f'Serie de residuos: {variable} (Ventana: {ventana})')
plt.xlabel('Tiempo')
plt.ylabel('Residuos')
# QQPlot
plt.subplot(1, 3, 2)
sns.histplot(residuos, kde=True)
plt.title('Histograma de residuos')
# ACF
plt.subplot(1, 3, 3)
from statsmodels.graphics.tsaplots import plot_acf
plot_acf(residuos, lags=30, ax=plt.gca())
plt.title('ACF de residuos')
plt.tight_layout()
plt.show()
return pd.DataFrame(resultados)
# Evaluar cada variable y combinar los resultados
resultados_totales = pd.concat([evaluar_variable_con_residuos('At'),
evaluar_variable_con_residuos('Price'),
evaluar_variable_con_residuos('Volatilidad_14')],
ignore_index=True)
# Mostrar la tabla con los resultados totales
print(resultados_totales)
Variable Ventana MAPE MAE RMSE \
0 At 7 2.054552e-14 8.881784e-16 8.881784e-16
1 At 14 2.054552e-14 8.881784e-16 8.881784e-16
2 At 21 2.054552e-14 8.881784e-16 8.881784e-16
3 At 28 2.054552e-14 8.881784e-16 8.881784e-16
4 Price 7 0.000000e+00 0.000000e+00 0.000000e+00
5 Price 14 0.000000e+00 0.000000e+00 0.000000e+00
6 Price 21 0.000000e+00 0.000000e+00 0.000000e+00
7 Price 28 0.000000e+00 0.000000e+00 0.000000e+00
8 Volatilidad_14 7 inf 2.318743e-19 2.318743e-19
9 Volatilidad_14 14 inf 2.318743e-19 2.318743e-19
10 Volatilidad_14 21 inf 2.318743e-19 2.318743e-19
11 Volatilidad_14 28 inf 2.318743e-19 2.318743e-19
MSE R2 Ljung-Box (p-value) Jarque-Bera (p-value)
0 7.888609e-31 0.0 0.0 1.968244e-63
1 7.888609e-31 0.0 0.0 1.968244e-63
2 7.888609e-31 0.0 0.0 1.968244e-63
3 7.888609e-31 0.0 0.0 1.968244e-63
4 0.000000e+00 1.0 0.0 0.000000e+00
5 0.000000e+00 1.0 0.0 0.000000e+00
6 0.000000e+00 1.0 0.0 0.000000e+00
7 0.000000e+00 1.0 0.0 0.000000e+00
8 5.376570e-38 0.0 0.0 0.000000e+00
9 5.376570e-38 0.0 0.0 0.000000e+00
10 5.376570e-38 0.0 0.0 0.000000e+00
11 5.376570e-38 0.0 0.0 0.000000e+00
A continuación se presenta la tabla de resultados utilizando suavizado exponencial simple. Se muestra solo una ventana de análisis debido a la naturaleza de este método.
Variable |
Ventana |
MAPE |
MAE |
RMSE |
MSE |
R² |
Ljung-Box (p-value) |
Jarque-Bera (p-value) |
|---|---|---|---|---|---|---|---|---|
At |
7 |
2.054552e-14 |
8.881784e-16 |
8.881784e-16 |
7.888609e-31 |
0.0 |
0.0 |
1.968244e-63 |
Price |
7 |
0.000000e+00 |
0.000000e+00 |
0.000000e+00 |
0.000000e+00 |
1.0 |
0.0 |
0.000000e+00 |
Volatilidad_14 |
7 |
inf |
2.318743e-19 |
2.318743e-19 |
5.376570e-38 |
0.0 |
0.0 |
0.000000e+00 |
Los resultados presentados en la tabla anterior reflejan el rendimiento de un modelo de suavizado exponencial simple, analizando específicamente los residuos generados por el modelo. Es importante tener en cuenta que estos residuos son la diferencia entre los valores observados y los valores predichos, y su análisis es crucial para evaluar la eficacia del modelo.
Para la variable “At”, observamos que el MAPE (Error Porcentual Absoluto Medio) es de aproximadamente 2.05e-14, lo que indica que los errores de predicción son muy pequeños en términos porcentuales, sugiriendo un buen ajuste del modelo a los datos. El MAE y el RMSE, ambos en el orden de 8.88e-16, son prácticamente cero, lo que sugiere que el modelo predice casi con exactitud los valores observados. Sin embargo, el valor del R² es 0.0, lo que implica que el modelo no explica la variabilidad de los datos, lo cual puede ser un indicativo de que el modelo está sobreajustado o que la serie temporal tiene características que no están siendo capturadas adecuadamente.
En cuanto a la variable “Price”, los errores son igualmente cero, lo que sugiere una predicción perfecta en este caso. Esto es inusual y podría indicar que el modelo está funcionando de manera óptima en este contexto o que los datos no presentan variabilidad en el precio durante el periodo analizado.
Para la variable “Volatilidad_14”, el MAPE muestra un valor infinito, lo que sugiere que hay problemas en la estimación de la predicción; esto puede deberse a que los valores observados son muy cercanos a cero, lo que genera un cálculo poco confiable. Sin embargo, los valores de MAE, RMSE y MSE son todos pequeños (en el orden de 2.32e-19 y 5.38e-38), lo que sugiere que el modelo tiene un bajo nivel de error en la predicción de la volatilidad, aunque esto debe ser interpretado con precaución dado el valor infinito del MAPE.
Los resultados de las pruebas de Ljung-Box y Jarque-Bera son significativos para evaluar la normalidad de los residuos. En este caso, un p-valor de 0.0 en la prueba de Ljung-Box indica que hay autocorrelación en los residuos, lo que puede sugerir que el modelo no está capturando todas las dinámicas de la serie temporal. Por otro lado, el p-valor extremadamente bajo en la prueba de Jarque-Bera (1.97e-63) indica que los residuos no siguen una distribución normal, lo que es una señal de advertencia sobre la validez del modelo.
El análisis de residuos del modelo de suavizado exponencial para las variables de retornos acumulados, precio y volatilidad en los cuatro horizontes temporales (7, 14, 21 y 28 días)#
En el caso de los retornos acumulados, la serie de residuos a lo largo del tiempo muestra fluctuaciones significativas, lo cual indica que estos residuos no son completamente aleatorios. Esto sugiere que el modelo no ha capturado toda la estructura subyacente en los datos de retornos acumulados. Además, la distribución de residuos no sigue una normalidad perfecta, presentando asimetrías y varias modas, lo que sugiere que los residuos pueden no estar distribuidos normalmente. Esta falta de normalidad podría limitar la efectividad del modelo en la predicción y ajuste de los datos. Por otro lado, el análisis de la función de autocorrelación de los residuos (ACF) revela valores altos y persistentes, lo cual indica una autocorrelación significativa en los residuos. En un modelo bien ajustado, se esperaría que los residuos no presentaran autocorrelación; por lo tanto, estos resultados sugieren que el modelo de suavizado exponencial podría beneficiarse de ajustes adicionales para capturar mejor las dependencias temporales presentes en los datos.
Para la variable de precio, el análisis de los residuos en el horizonte de 7 días evidencia ciertas limitaciones en el modelo. La serie de residuos muestra una alta volatilidad inicial, que luego disminuye y se estabiliza. Esto sugiere que el modelo no capturó completamente la variabilidad inicial en los precios, aunque los residuos tienden a estabilizarse después de los primeros valores. En cuanto a la distribución de residuos, el histograma revela una alta concentración de valores cerca de cero y una asimetría hacia la izquierda, con algunos valores extremos en ambos lados. Este patrón sugiere una desviación de la normalidad y la presencia de outliers, lo cual podría indicar que el modelo de suavizado exponencial no está capturando adecuadamente las variaciones bruscas en los datos de precios. Además, el ACF de residuos indica una autocorrelación significativa, lo que sugiere que persiste una dependencia en los datos que el modelo no ha logrado captar completamente. Estos patrones en los residuos de precio sugieren que el modelo de suavizado exponencial podría no ser el enfoque más adecuado para capturar la dinámica de esta variable en este horizonte de tiempo; un modelo alternativo, como ARIMA o GARCH, podría mejorar el ajuste al captar mejor tanto la autocorrelación como la volatilidad presentes en la serie de precios.
Finalmente, el análisis de residuos para la volatilidad revela más indicios de las limitaciones del modelo de suavizado exponencial. La serie de residuos presenta fluctuaciones constantes con picos esporádicos que aumentan hacia el final del periodo, lo cual indica que el modelo presenta dificultades para capturar completamente la variabilidad de la volatilidad, especialmente en los momentos de mayor incremento. Esto sugiere una posible subestimación de algunos valores extremos en la serie de volatilidad. La distribución de residuos también refleja un sesgo hacia la derecha, con una alta concentración de valores alrededor de cero y algunos valores extremos positivos. Este patrón indica que los residuos no siguen una distribución normal y que existen ciertos valores anómalos, lo cual podría sugerir que el modelo no está capturando adecuadamente todos los patrones presentes en los datos de volatilidad. Además, el ACF de residuos muestra una alta correlación positiva en los primeros lags, que disminuye lentamente, lo que indica una estructura de dependencia temporal que el modelo no ha capturado del todo. Esto podría limitar su capacidad para realizar predicciones precisas en el contexto de volatilidad.
Residuos de la prediccion#
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from statsmodels.stats.diagnostic import acorr_ljungbox
# Función para calcular el MAPE
def MAPE(y_true, y_pred):
return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
# Función para realizar predicciones con Simple Exponential Smoothing
def ses_predict(serie, alpha, test_size):
ses_pred = [serie.iloc[0]] # Usar el primer valor de la serie
for t in range(1, len(serie)):
pred = alpha * serie.iloc[t] + (1 - alpha) * ses_pred[-1]
ses_pred.append(pred)
pred_val = []
for t in range(test_size):
pred = alpha * serie.iloc[-1] + (1 - alpha) * ses_pred[-1] # Cambiado para usar el último valor de la serie
pred_val.append(pred)
return pred_val
# Función para evaluar el modelo en el conjunto de test y analizar residuos
def evaluar_modelo_en_conjunto_test(variable, alpha, test_size):
train_size = len(df) - test_size
# Serie de tiempo de entrenamiento sin NaNs
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
# Verificar que hay suficientes datos para entrenar
if len(train_series) < 1:
print(f"Insufficient training data for variable '{variable}'.")
return None
# Obtener las predicciones para el conjunto de validación
pred_val = ses_predict(train_series, alpha, test_size)
# Conjunto de validación real (últimos test_size valores de la serie) sin NaNs
val_series = df[variable].iloc[-test_size:].dropna().reset_index(drop=True)
# Calcular los residuos
residuos = val_series - pred_val
# Calcular métricas
mae = mean_absolute_error(val_series, pred_val)
mse = mean_squared_error(val_series, pred_val)
rmse = np.sqrt(mse)
mape = MAPE(val_series, pred_val)
r2 = r2_score(val_series, pred_val)
# Prueba de Ljung-Box
ljung_box_results = acorr_ljungbox(residuos, lags=[10], return_df=True)
ljung_box_pvalue = ljung_box_results['lb_pvalue'].values[0] if not ljung_box_results.empty else np.nan
# Guardar resultados
return {
'Variable': variable,
'MAPE': mape,
'MAE': mae,
'RMSE': rmse,
'MSE': mse,
'R2': r2,
'Ljung-Box (p-value)': ljung_box_pvalue
}
# Variables y parámetros a evaluar
variables = ['At', 'Price', 'Volatilidad_14']
alpha = 0.5
test_size = 28
# Evaluar cada variable y almacenar los resultados
resultados = []
for variable in variables:
resultado = evaluar_modelo_en_conjunto_test(variable, alpha, test_size)
if resultado is not None:
resultados.append(resultado)
# Convertir los resultados en un DataFrame
resultados_df = pd.DataFrame(resultados)
# Mostrar la tabla con los resultados
print(resultados_df)
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\stattools.py:702: RuntimeWarning:
invalid value encountered in divide
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\stattools.py:702: RuntimeWarning:
invalid value encountered in divide
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\stattools.py:702: RuntimeWarning:
invalid value encountered in divide
Variable MAPE MAE RMSE MSE \
0 At 2.054552e-14 8.881784e-16 8.881784e-16 7.888609e-31
1 Price 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00
2 Volatilidad_14 inf 2.318743e-19 2.318743e-19 5.376570e-38
R2 Ljung-Box (p-value)
0 0.0 NaN
1 1.0 NaN
2 0.0 NaN
Variable |
MAPE |
MAE |
RMSE |
MSE |
R² |
Ljung-Box (p-value) |
|---|---|---|---|---|---|---|
At |
2.054552e-14 |
8.881784e-16 |
8.881784e-16 |
7.888609e-31 |
0.0 |
NaN |
Price |
0.000000e+00 |
0.000000e+00 |
0.000000e+00 |
0.000000e+00 |
1.0 |
NaN |
Volatilidad_14 |
inf |
2.318743e-19 |
2.318743e-19 |
5.376570e-38 |
0.0 |
NaN |
Para la variable “At”, el MAPE es de aproximadamente 2.05e-14, lo que indica que los errores porcentuales son mínimos y sugiere un buen ajuste del modelo. Los valores de MAE y RMSE son extremadamente bajos (8.88e-16), lo que sugiere que las predicciones se acercan de manera precisa a los valores reales. Además, el MSE de 7.89e-31 refuerza la idea de un ajuste preciso. Sin embargo, el R² de 0.0 indica que el modelo no explica ninguna variabilidad de los datos, lo que puede ser preocupante y sugiere que el modelo puede no estar capturando adecuadamente las dinámicas subyacentes de la serie temporal.
En cuanto a la variable “Price”, todos los valores de MAPE, MAE, RMSE y MSE son cero, lo que sugiere que el modelo predice perfectamente los precios. Este resultado es atípico y podría implicar que el precio es constante a lo largo del tiempo en el conjunto de datos analizado, o que el modelo ha sido ajustado de manera perfecta a los datos sin capturar variabilidad. El R² de 1.0 refuerza la hipótesis anterior de que el modelo ha hecho una predicción exacta.
Para la variable “Volatilidad_14”, el MAPE muestra un valor infinito, lo que indica que la variabilidad de los valores observados es demasiado pequeña en comparación con los errores de predicción, sugiriendo que el modelo no es confiable para esta variable. Aunque los valores de MAE, RMSE y MSE son bajos (en el orden de 2.32e-19 y 5.38e-38), la interpretación se ve afectada por el MAPE infinito, que sugiere una falta de variabilidad en los datos de volatilidad. Además, el R² de 0.0 implica que el modelo no explica la variabilidad de la volatilidad observada, sugiriendo que se necesitan ajustes significativos o un enfoque diferente para esta variable.
Simple Exponential Smoothing (Usando statsmodels.tsa.holtwinters)#
import pandas as pd
import numpy as np
from statsmodels.tsa.holtwinters import SimpleExpSmoothing
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from statsmodels.stats.diagnostic import acorr_ljungbox
# Parámetros
alphas = [0.5] # Factores de suavización que queremos probar
ventanas = [7, 14, 21, 28] # Ventanas móviles para cada predicción
variables = ['Price', 'Volatilidad_14', 'At']
# Almacenaremos las predicciones
predicciones = {}
# Dividimos el conjunto de validación y entrenamiento
train_size = len(df) - 28
test_size = 28
# Para cada variable y cada ventana
for variable in variables:
predicciones[variable] = {}
# Tomamos el conjunto de entrenamiento para esa variable
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
for ventana in ventanas:
# Inicializar diccionario para cada ventana
predicciones[variable][ventana] = {}
for alpha in alphas:
# Aplicar SES usando SimpleExpSmoothing de statsmodels
model = SimpleExpSmoothing(train_series)
model_fit = model.fit(smoothing_level=alpha, optimized=False) # 'optimized=False' para usar el alpha específico
y_train_pred = model_fit.fittedvalues
# Ahora realizamos predicciones para el conjunto de validación (últimos test_size datos)
val_predictions = model_fit.forecast(test_size)
# Guardar las predicciones para esta ventana y alpha
predicciones[variable][ventana][alpha] = val_predictions
import numpy as np
import pandas as pd
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from statsmodels.tsa.holtwinters import SimpleExpSmoothing
# Función para calcular el MAPE
def MAPE(y_true, y_pred):
return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
# Función para realizar predicciones con Simple Exponential Smoothing
def ses_predict(serie, alpha, test_size):
model = SimpleExpSmoothing(serie)
model_fit = model.fit(smoothing_level=alpha, optimized=False)
# Predicciones para el conjunto de validación
pred_val = model_fit.forecast(test_size)
return pred_val
# Función para evaluar una variable
def evaluar_variable(variable):
ventanas = [7, 14, 21, 28]
alphas = [0.5]
train_size = len(df) - 28
test_size = 28
resultados = []
# Evaluar el SES para la variable
for ventana in ventanas:
for alpha in alphas:
# Serie de tiempo de entrenamiento sin NaNs
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
# Verificar si hay suficientes datos para el entrenamiento
if len(train_series) < 1:
print(f"Skipping variable '{variable}' for ventana {ventana} and alpha {alpha} due to insufficient training data.")
continue
# Obtener las predicciones para el conjunto de validación
pred_val = ses_predict(train_series, alpha, test_size)
# Conjunto de validación real (últimos 28 valores de la serie) sin NaNs
val_series = df[variable].iloc[-test_size:].dropna().reset_index(drop=True)
# Verificar si hay suficientes datos para la validación
if len(val_series) < test_size:
print(f"Skipping variable '{variable}' for ventana {ventana} and alpha {alpha} due to insufficient validation data.")
continue
# Calcular las métricas
mae = mean_absolute_error(val_series, pred_val)
mse = mean_squared_error(val_series, pred_val)
rmse = np.sqrt(mse)
mape = MAPE(val_series, pred_val)
r2 = r2_score(val_series, pred_val)
# Guardar los resultados en una lista
resultados.append({
'Variable': variable,
'Ventana': ventana,
'Alpha': alpha,
'MAE': mae,
'MSE': mse,
'RMSE': rmse,
'MAPE': mape,
'R2': r2
})
return pd.DataFrame(resultados)
# Evaluar cada variable y combinar los resultados
resultados_totales = pd.concat([evaluar_variable('At'),
evaluar_variable('Price'),
evaluar_variable('Volatilidad_14')],
ignore_index=True)
# Mostrar la tabla con los resultados totales
print(resultados_totales)
Variable Ventana Alpha MAE MSE RMSE \
0 At 7 0.5 8.881784e-16 7.888609e-31 8.881784e-16
1 At 14 0.5 8.881784e-16 7.888609e-31 8.881784e-16
2 At 21 0.5 8.881784e-16 7.888609e-31 8.881784e-16
3 At 28 0.5 8.881784e-16 7.888609e-31 8.881784e-16
4 Price 7 0.5 0.000000e+00 0.000000e+00 0.000000e+00
5 Price 14 0.5 0.000000e+00 0.000000e+00 0.000000e+00
6 Price 21 0.5 0.000000e+00 0.000000e+00 0.000000e+00
7 Price 28 0.5 0.000000e+00 0.000000e+00 0.000000e+00
8 Volatilidad_14 7 0.5 4.637486e-19 2.150628e-37 4.637486e-19
9 Volatilidad_14 14 0.5 4.637486e-19 2.150628e-37 4.637486e-19
10 Volatilidad_14 21 0.5 4.637486e-19 2.150628e-37 4.637486e-19
11 Volatilidad_14 28 0.5 4.637486e-19 2.150628e-37 4.637486e-19
MAPE R2
0 NaN 0.0
1 NaN 0.0
2 NaN 0.0
3 NaN 0.0
4 NaN 1.0
5 NaN 1.0
6 NaN 1.0
7 NaN 1.0
8 NaN 0.0
9 NaN 0.0
10 NaN 0.0
11 NaN 0.0
A continuación se presenta la tabla de resultados utilizando suavizado exponencial simple, donde se muestra solo una ventana de análisis por la naturaleza del método.
Variable |
Ventana |
Alpha |
MAE |
MSE |
RMSE |
MAPE |
R2 |
|---|---|---|---|---|---|---|---|
At |
7 |
0.5 |
8.881784e-16 |
7.888609e-31 |
8.881784e-16 |
NaN |
0.0 |
Price |
7 |
0.5 |
0.000000e+00 |
0.000000e+00 |
0.000000e+00 |
NaN |
1.0 |
Volatilidad_14 |
7 |
0.5 |
4.637486e-19 |
2.150628e-37 |
4.637486e-19 |
NaN |
0.0 |
Los resultados obtenidos a partir del análisis utilizando suavizado exponencial simple se presentan en la tabla anterior, donde se incluye solo una ventana de análisis debido a la naturaleza de este método. Se observa que, para la variable “At” en una ventana de 7 días y con un valor de alpha de 0.5, el error absoluto medio (MAE) es de aproximadamente 8.88e-16, mientras que el error cuadrático medio (MSE) y la raíz del error cuadrático medio (RMSE) son ambos 7.89e-31 y 8.88e-16, respectivamente. Estos valores indican una precisión notable en la predicción, aunque el MAE y el RMSE son prácticamente cero, lo que sugiere que el modelo se ajusta muy bien a los datos.
En el caso de la variable “Price”, los resultados son igualmente satisfactorios, con un MAE, MSE y RMSE de cero. Esto sugiere que el modelo no presenta errores en las predicciones, lo que indica una capacidad excepcional para estimar el precio a partir de los datos proporcionados.
Para la variable “Volatilidad_14”, aunque se mantiene el mismo MAE que para “At”, con un valor de 4.64e-19, el MSE es extremadamente bajo, 2.15e-37, lo que implica una buena consistencia en las predicciones. Sin embargo, el valor de R² para esta variable es 0.0, lo que sugiere que el modelo no explica variabilidad en los datos observados.
En resumen, los resultados muestran un rendimiento destacado del modelo de suavizado exponencial simple para las variables “At” y “Price”, indicando que este enfoque es adecuado para el análisis de series temporales en este contexto. Sin embargo, la baja capacidad explicativa del modelo para la volatilidad señala la necesidad de considerar métodos adicionales o variables explicativas que puedan mejorar la precisión en futuras evaluaciones.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from statsmodels.stats.diagnostic import acorr_ljungbox
from statsmodels.stats.stattools import jarque_bera
from statsmodels.tsa.holtwinters import SimpleExpSmoothing
import statsmodels.api as sm
# Función para calcular el MAPE
def MAPE(y_true, y_pred):
return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
# Función para realizar predicciones con Simple Exponential Smoothing
def ses_predict(serie, alpha, test_size):
model = SimpleExpSmoothing(serie)
model_fit = model.fit(smoothing_level=alpha, optimized=False)
pred_val = model_fit.forecast(test_size)
return pred_val, model_fit.resid # Retornar también los residuos
# Función para evaluar la variable y analizar residuos
def evaluar_variable_con_residuos(variable):
alphas = [0.5]
train_size = len(df) - 28
test_size = 28
resultados = []
for alpha in alphas:
# Serie de tiempo de entrenamiento sin NaNs
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
# Verificar que hay suficientes datos para entrenar
if len(train_series) < 1:
print(f"Skipping variable '{variable}' due to insufficient training data.")
continue
# Obtener las predicciones y residuos para el conjunto de validación
pred_val, residuos = ses_predict(train_series, alpha, test_size)
# Conjunto de validación real (últimos 28 valores de la serie) sin NaNs
val_series = df[variable].iloc[-test_size:].dropna().reset_index(drop=True)
# Calcular métricas
mae = mean_absolute_error(val_series, pred_val)
mse = mean_squared_error(val_series, pred_val)
rmse = np.sqrt(mse)
mape = MAPE(val_series, pred_val)
r2 = r2_score(val_series, pred_val)
# Pruebas de hipótesis
ljung_box_results = acorr_ljungbox(residuos, lags=[10], return_df=True)
ljung_box_pvalue = ljung_box_results['lb_pvalue'].values[0] if not ljung_box_results.empty else np.nan
jarque_bera_results = jarque_bera(residuos)
jarque_bera_stat = jarque_bera_results[0]
jarque_bera_pvalue = jarque_bera_results[1]
# Guardar resultados
resultados.append({
'Variable': variable,
'MAPE': mape,
'MAE': mae,
'RMSE': rmse,
'MSE': mse,
'R2': r2,
'Ljung-Box (p-value)': ljung_box_pvalue,
'Jarque-Bera (p-value)': jarque_bera_pvalue
})
# Gráficos
plt.figure(figsize=(15, 5))
# Serie de residuos
plt.subplot(1, 3, 1)
plt.plot(residuos)
plt.title(f'Serie de residuos: {variable}')
plt.xlabel('Tiempo')
plt.ylabel('Residuos')
# QQPlot
plt.subplot(1, 3, 2)
sm.qqplot(residuos, line='s', ax=plt.gca())
plt.title('QQPlot de residuos')
# ACF
plt.subplot(1, 3, 3)
from statsmodels.graphics.tsaplots import plot_acf
plot_acf(residuos, lags=30, ax=plt.gca())
plt.title('ACF de residuos')
plt.tight_layout()
plt.show()
return pd.DataFrame(resultados)
# Evaluar cada variable y combinar los resultados
resultados_totales = pd.concat([evaluar_variable_con_residuos('At'),
evaluar_variable_con_residuos('Price'),
evaluar_variable_con_residuos('Volatilidad_14')],
ignore_index=True)
# Mostrar la tabla con los resultados totales
print(resultados_totales)
Variable MAPE MAE RMSE MSE R2 \
0 At NaN 8.881784e-16 8.881784e-16 7.888609e-31 0.0
1 Price NaN 0.000000e+00 0.000000e+00 0.000000e+00 1.0
2 Volatilidad_14 NaN 4.637486e-19 4.637486e-19 2.150628e-37 0.0
Ljung-Box (p-value) Jarque-Bera (p-value)
0 6.386049e-299 0.0
1 0.000000e+00 0.0
2 0.000000e+00 0.0
El análisis de los residuos generados por el modelo de suavizado exponencial, implementado a través de la librería statsmodels, para las variables de retornos acumulados, precio y volatilidad en los cuatro horizontes temporales (7, 14, 21 y 28 días)#
Para la variable de retornos acumulados, la distribución de residuos en el tiempo muestra una estabilidad notable a lo largo del gráfico, con algunos picos ocasionales, pero manteniéndose generalmente entre 2.5 y -2.5. Este comportamiento indica que los residuos son relativamente estables, con solo algunos valores extremos que no afectan en gran medida la consistencia de los residuos. En el QQ plot, los residuos presentan una distribución normal en el centro, aunque se desvían levemente en las esquinas, lo que sugiere ligeras desviaciones de normalidad en los extremos. Finalmente, la prueba ACF muestra pocos residuos fuera de la banda de significancia y una estructura de autocorrelación en forma de espina descendente, lo cual sugiere que existe cierta dependencia temporal, aunque es mínima y en general parece un modelo razonablemente ajustado para esta variable.
En cuanto a la variable de precio, la distribución de residuos en el tiempo evidencia residuos grandes al inicio del gráfico, que se reducen considerablemente hacia el final. Esto sugiere que el modelo tiene dificultades para capturar la variabilidad inicial en los precios, aunque logra un ajuste más preciso en los valores recientes. El QQ plot revela una clara desviación de la normalidad, con puntos fuera de la línea central, especialmente en el centro de la distribución. Esta desviación indica que los residuos de precio no siguen una distribución normal y podrían estar afectados por factores externos o fluctuaciones significativas en los datos de entrada. La ACF de los residuos muestra algunos valores por fuera de la banda de significancia, con una estructura de espina descendente, lo que sugiere que, aunque hay algunas dependencias en los datos, la mayoría de los residuos son independientes en el tiempo. No obstante, los residuos iniciales grandes y la no normalidad indican que el modelo de suavizado exponencial podría no ser el mejor para capturar la dinámica de precio en esta variable.
Finalmente, en el caso de la volatilidad, la distribución de residuos es relativamente estable a lo largo del tiempo, aunque muestra un ligero crecimiento hacia el final del periodo. Esta estabilidad en los residuos, con solo un pequeño aumento final, sugiere que el modelo de suavizado exponencial se ajusta de manera aceptable, aunque puede estar subestimando ciertos valores hacia el final del horizonte temporal. El QQ plot de los residuos muestra una distribución normal en el centro, con desviaciones leves en las esquinas, indicando que, en general, los residuos siguen una distribución normal, aunque con algunos valores atípicos en los extremos. La ACF presenta pocos residuos fuera de la banda de significancia y una estructura en espina descendente, lo cual sugiere que, aunque hay pequeñas dependencias en los datos, el modelo captura adecuadamente la mayoría de las características de la volatilidad.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from statsmodels.stats.diagnostic import acorr_ljungbox
from statsmodels.tsa.holtwinters import SimpleExpSmoothing
# Función para calcular el MAPE
def MAPE(y_true, y_pred):
return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
# Función para realizar predicciones con Simple Exponential Smoothing
def ses_predict(serie, alpha, test_size):
model = SimpleExpSmoothing(serie)
model_fit = model.fit(smoothing_level=alpha, optimized=False)
pred_val = model_fit.forecast(test_size)
return pred_val, model_fit.resid # Retornar también los residuos
# Función para evaluar el modelo en el conjunto de prueba
def evaluar_modelo_en_test(variable):
alphas = [0.5]
train_size = len(df) - 28 # Tamaño del conjunto de entrenamiento
test_size = 28 # Tamaño del conjunto de prueba
resultados = []
for alpha in alphas:
# Serie de tiempo de entrenamiento sin NaNs
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
# Obtener las predicciones y residuos para el conjunto de prueba
pred_val, residuos = ses_predict(train_series, alpha, test_size)
# Conjunto de prueba real (últimos 28 valores de la serie) sin NaNs
val_series = df[variable].iloc[-test_size:].dropna().reset_index(drop=True)
# Calcular métricas
mae = mean_absolute_error(val_series, pred_val)
mse = mean_squared_error(val_series, pred_val)
rmse = np.sqrt(mse)
mape = MAPE(val_series, pred_val)
r2 = r2_score(val_series, pred_val)
# Pruebas de hipótesis
ljung_box_results = acorr_ljungbox(residuos, lags=[10], return_df=True)
ljung_box_pvalue = ljung_box_results['lb_pvalue'].values[0] if not ljung_box_results.empty else np.nan
# Guardar resultados
resultados.append({
'Variable': variable,
'MAPE': mape,
'MAE': mae,
'RMSE': rmse,
'MSE': mse,
'R2': r2,
'Ljung-Box (p-value)': ljung_box_pvalue
})
return pd.DataFrame(resultados)
# Evaluar cada variable y combinar los resultados
resultados_totales_test = pd.concat([evaluar_modelo_en_test('At'),
evaluar_modelo_en_test('Price'),
evaluar_modelo_en_test('Volatilidad_14')],
ignore_index=True)
# Mostrar la tabla con los resultados totales del conjunto de prueba
print(resultados_totales_test)
Variable MAPE MAE RMSE MSE R2 \
0 At NaN 8.881784e-16 8.881784e-16 7.888609e-31 0.0
1 Price NaN 0.000000e+00 0.000000e+00 0.000000e+00 1.0
2 Volatilidad_14 NaN 4.637486e-19 4.637486e-19 2.150628e-37 0.0
Ljung-Box (p-value)
0 6.386049e-299
1 0.000000e+00
2 0.000000e+00
Variable |
MAPE |
MAE |
RMSE |
MSE |
R² |
Ljung-Box (p-value) |
|---|---|---|---|---|---|---|
At |
NaN |
8.881784e-16 |
8.881784e-16 |
7.888609e-31 |
0.0 |
6.386049e-299 |
Price |
NaN |
0.000000e+00 |
0.000000e+00 |
0.000000e+00 |
1.0 |
0.000000e+00 |
Volatilidad_14 |
NaN |
4.637486e-19 |
4.637486e-19 |
2.150628e-37 |
0.0 |
0.000000e+00 |
Para la variable “At”, los valores de MAE (8.88e-16) y RMSE (8.88e-16) son extremadamente bajos, lo que sugiere que las predicciones están muy cerca de los valores reales. Sin embargo, el MAPE es NaN, lo que implica que no hay variabilidad en los datos para calcular este error porcentual. El MSE también es muy pequeño (7.89e-31), indicando que los errores de predicción son mínimos. El R² de 0.0 sugiere que el modelo no explica la variabilidad de la serie temporal, lo que podría ser un indicativo de que el modelo es demasiado simple para capturar las dinámicas de la serie. Además, el p-valor de Ljung-Box (6.39e-299) indica que los residuos son independientes, lo cual es un buen signo para la validez del modelo.
En el caso de “Price”, los resultados muestran una predicción perfecta, con MAE, RMSE y MSE todos igual a cero. Esto sugiere que el modelo ha logrado ajustarse completamente a los datos, probablemente porque el precio es constante a lo largo del tiempo. El R² de 1.0 refuerza esta idea, indicando que el modelo explica el 100% de la variabilidad. Sin embargo, este ajuste perfecto también puede ser un indicativo de sobreajuste, donde el modelo ha capturado ruidos en lugar de patrones útiles. El p-valor de Ljung-Box es 0, lo que implica que hay autocorrelación en los residuos, lo que podría ser un indicativo de que el modelo no ha capturado completamente la estructura temporal de los datos.
Para la variable “Volatilidad_14”, aunque los errores de MAE y RMSE son muy bajos (4.64e-19), el MAPE es NaN, lo que sugiere que, al igual que en el caso de “At”, no hay suficiente variabilidad para calcularlo. El MSE es también bajo (2.15e-37), indicando que los errores de predicción son pequeños. Sin embargo, el R² de 0.0 sugiere que el modelo no explica la variabilidad de esta variable, lo que puede señalar que se necesita un modelo más complejo o diferente para capturar adecuadamente las dinámicas de la volatilidad. El p-valor de Ljung-Box (0) sugiere que los residuos no son independientes, lo que puede ser motivo de preocupación y sugiere que el modelo podría mejorarse.
Doble suavizado exponencial sin libreria#
import pandas as pd
import numpy as np
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from statsmodels.stats.diagnostic import acorr_ljungbox
# Parámetros
alphas = [0.5] # Factores de suavización que queremos probar
ventanas = [7, 14, 21, 28] # Ventanas móviles para cada predicción
variables = ['Price', 'Volatilidad_14', 'At']
# Función para realizar Double Exponential Smoothing
def double_exponential_smoothing(series, alpha, beta, n_forecast):
level = series[0]
trend = series[1] - series[0] # Inicializar tendencia
result = [level + trend] # Primer pronóstico
# Lista para almacenar los valores de nivel y tendencia
levels = [level]
trends = [trend]
# Suavizado
for t in range(1, len(series)):
last_level = level
level = alpha * series[t] + (1 - alpha) * (last_level + trend)
trend = beta * (level - last_level) + (1 - beta) * trend
levels.append(level)
trends.append(trend)
# Pronóstico
if t >= len(series) - 1:
forecast = level + trend
result.append(forecast)
# Pronósticos para los próximos n períodos
forecasts = []
for i in range(n_forecast):
forecast = level + (i + 1) * trend
forecasts.append(forecast)
return forecasts, result, levels, trends
# Almacenaremos las predicciones
predicciones = {}
# Dividimos el conjunto de validación y entrenamiento
train_size = len(df) - 28
test_size = 28
# Para cada variable y cada ventana
for variable in variables:
predicciones[variable] = {}
# Tomamos el conjunto de entrenamiento para esa variable
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
for ventana in ventanas:
# Inicializar diccionario para cada ventana
predicciones[variable][ventana] = {}
for alpha in alphas:
# Definir un beta (puedes experimentar con este valor)
beta = 0.5
# Aplicar Double Exponential Smoothing
val_predictions, y_train_pred, levels, trends = double_exponential_smoothing(train_series.values, alpha, beta, test_size)
# Guardar las predicciones para esta ventana y alpha
predicciones[variable][ventana][alpha] = val_predictions
# Mostrar predicciones
for variable in predicciones:
for ventana in predicciones[variable]:
print(f"Predicciones para {variable} con ventana {ventana}: {predicciones[variable][ventana]}")
Predicciones para Price con ventana 7: {0.5: [np.float64(0.0999999999994), np.float64(0.09999999999986978), np.float64(0.10000000000033957), np.float64(0.10000000000080936), np.float64(0.10000000000127914), np.float64(0.10000000000174893), np.float64(0.10000000000221872), np.float64(0.1000000000026885), np.float64(0.10000000000315828), np.float64(0.10000000000362808), np.float64(0.10000000000409785), np.float64(0.10000000000456764), np.float64(0.10000000000503743), np.float64(0.10000000000550721), np.float64(0.100000000005977), np.float64(0.1000000000064468), np.float64(0.10000000000691657), np.float64(0.10000000000738636), np.float64(0.10000000000785615), np.float64(0.10000000000832593), np.float64(0.10000000000879572), np.float64(0.10000000000926551), np.float64(0.10000000000973529), np.float64(0.10000000001020508), np.float64(0.10000000001067487), np.float64(0.10000000001114465), np.float64(0.10000000001161444), np.float64(0.10000000001208423)]}
Predicciones para Price con ventana 14: {0.5: [np.float64(0.0999999999994), np.float64(0.09999999999986978), np.float64(0.10000000000033957), np.float64(0.10000000000080936), np.float64(0.10000000000127914), np.float64(0.10000000000174893), np.float64(0.10000000000221872), np.float64(0.1000000000026885), np.float64(0.10000000000315828), np.float64(0.10000000000362808), np.float64(0.10000000000409785), np.float64(0.10000000000456764), np.float64(0.10000000000503743), np.float64(0.10000000000550721), np.float64(0.100000000005977), np.float64(0.1000000000064468), np.float64(0.10000000000691657), np.float64(0.10000000000738636), np.float64(0.10000000000785615), np.float64(0.10000000000832593), np.float64(0.10000000000879572), np.float64(0.10000000000926551), np.float64(0.10000000000973529), np.float64(0.10000000001020508), np.float64(0.10000000001067487), np.float64(0.10000000001114465), np.float64(0.10000000001161444), np.float64(0.10000000001208423)]}
Predicciones para Price con ventana 21: {0.5: [np.float64(0.0999999999994), np.float64(0.09999999999986978), np.float64(0.10000000000033957), np.float64(0.10000000000080936), np.float64(0.10000000000127914), np.float64(0.10000000000174893), np.float64(0.10000000000221872), np.float64(0.1000000000026885), np.float64(0.10000000000315828), np.float64(0.10000000000362808), np.float64(0.10000000000409785), np.float64(0.10000000000456764), np.float64(0.10000000000503743), np.float64(0.10000000000550721), np.float64(0.100000000005977), np.float64(0.1000000000064468), np.float64(0.10000000000691657), np.float64(0.10000000000738636), np.float64(0.10000000000785615), np.float64(0.10000000000832593), np.float64(0.10000000000879572), np.float64(0.10000000000926551), np.float64(0.10000000000973529), np.float64(0.10000000001020508), np.float64(0.10000000001067487), np.float64(0.10000000001114465), np.float64(0.10000000001161444), np.float64(0.10000000001208423)]}
Predicciones para Price con ventana 28: {0.5: [np.float64(0.0999999999994), np.float64(0.09999999999986978), np.float64(0.10000000000033957), np.float64(0.10000000000080936), np.float64(0.10000000000127914), np.float64(0.10000000000174893), np.float64(0.10000000000221872), np.float64(0.1000000000026885), np.float64(0.10000000000315828), np.float64(0.10000000000362808), np.float64(0.10000000000409785), np.float64(0.10000000000456764), np.float64(0.10000000000503743), np.float64(0.10000000000550721), np.float64(0.100000000005977), np.float64(0.1000000000064468), np.float64(0.10000000000691657), np.float64(0.10000000000738636), np.float64(0.10000000000785615), np.float64(0.10000000000832593), np.float64(0.10000000000879572), np.float64(0.10000000000926551), np.float64(0.10000000000973529), np.float64(0.10000000001020508), np.float64(0.10000000001067487), np.float64(0.10000000001114465), np.float64(0.10000000001161444), np.float64(0.10000000001208423)]}
Predicciones para Volatilidad_14 con ventana 7: {0.5: [np.float64(-2.655992905380553e-10), np.float64(-2.758814620939934e-10), np.float64(-2.8616363364993157e-10), np.float64(-2.9644580520586977e-10), np.float64(-3.0672797676180796e-10), np.float64(-3.170101483177461e-10), np.float64(-3.2729231987368426e-10), np.float64(-3.3757449142962245e-10), np.float64(-3.4785666298556065e-10), np.float64(-3.581388345414988e-10), np.float64(-3.6842100609743694e-10), np.float64(-3.7870317765337514e-10), np.float64(-3.8898534920931334e-10), np.float64(-3.992675207652515e-10), np.float64(-4.0954969232118963e-10), np.float64(-4.1983186387712783e-10), np.float64(-4.3011403543306603e-10), np.float64(-4.4039620698900417e-10), np.float64(-4.506783785449423e-10), np.float64(-4.609605501008805e-10), np.float64(-4.712427216568187e-10), np.float64(-4.815248932127569e-10), np.float64(-4.918070647686951e-10), np.float64(-5.020892363246333e-10), np.float64(-5.123714078805714e-10), np.float64(-5.226535794365095e-10), np.float64(-5.329357509924477e-10), np.float64(-5.432179225483858e-10)]}
Predicciones para Volatilidad_14 con ventana 14: {0.5: [np.float64(-2.655992905380553e-10), np.float64(-2.758814620939934e-10), np.float64(-2.8616363364993157e-10), np.float64(-2.9644580520586977e-10), np.float64(-3.0672797676180796e-10), np.float64(-3.170101483177461e-10), np.float64(-3.2729231987368426e-10), np.float64(-3.3757449142962245e-10), np.float64(-3.4785666298556065e-10), np.float64(-3.581388345414988e-10), np.float64(-3.6842100609743694e-10), np.float64(-3.7870317765337514e-10), np.float64(-3.8898534920931334e-10), np.float64(-3.992675207652515e-10), np.float64(-4.0954969232118963e-10), np.float64(-4.1983186387712783e-10), np.float64(-4.3011403543306603e-10), np.float64(-4.4039620698900417e-10), np.float64(-4.506783785449423e-10), np.float64(-4.609605501008805e-10), np.float64(-4.712427216568187e-10), np.float64(-4.815248932127569e-10), np.float64(-4.918070647686951e-10), np.float64(-5.020892363246333e-10), np.float64(-5.123714078805714e-10), np.float64(-5.226535794365095e-10), np.float64(-5.329357509924477e-10), np.float64(-5.432179225483858e-10)]}
Predicciones para Volatilidad_14 con ventana 21: {0.5: [np.float64(-2.655992905380553e-10), np.float64(-2.758814620939934e-10), np.float64(-2.8616363364993157e-10), np.float64(-2.9644580520586977e-10), np.float64(-3.0672797676180796e-10), np.float64(-3.170101483177461e-10), np.float64(-3.2729231987368426e-10), np.float64(-3.3757449142962245e-10), np.float64(-3.4785666298556065e-10), np.float64(-3.581388345414988e-10), np.float64(-3.6842100609743694e-10), np.float64(-3.7870317765337514e-10), np.float64(-3.8898534920931334e-10), np.float64(-3.992675207652515e-10), np.float64(-4.0954969232118963e-10), np.float64(-4.1983186387712783e-10), np.float64(-4.3011403543306603e-10), np.float64(-4.4039620698900417e-10), np.float64(-4.506783785449423e-10), np.float64(-4.609605501008805e-10), np.float64(-4.712427216568187e-10), np.float64(-4.815248932127569e-10), np.float64(-4.918070647686951e-10), np.float64(-5.020892363246333e-10), np.float64(-5.123714078805714e-10), np.float64(-5.226535794365095e-10), np.float64(-5.329357509924477e-10), np.float64(-5.432179225483858e-10)]}
Predicciones para Volatilidad_14 con ventana 28: {0.5: [np.float64(-2.655992905380553e-10), np.float64(-2.758814620939934e-10), np.float64(-2.8616363364993157e-10), np.float64(-2.9644580520586977e-10), np.float64(-3.0672797676180796e-10), np.float64(-3.170101483177461e-10), np.float64(-3.2729231987368426e-10), np.float64(-3.3757449142962245e-10), np.float64(-3.4785666298556065e-10), np.float64(-3.581388345414988e-10), np.float64(-3.6842100609743694e-10), np.float64(-3.7870317765337514e-10), np.float64(-3.8898534920931334e-10), np.float64(-3.992675207652515e-10), np.float64(-4.0954969232118963e-10), np.float64(-4.1983186387712783e-10), np.float64(-4.3011403543306603e-10), np.float64(-4.4039620698900417e-10), np.float64(-4.506783785449423e-10), np.float64(-4.609605501008805e-10), np.float64(-4.712427216568187e-10), np.float64(-4.815248932127569e-10), np.float64(-4.918070647686951e-10), np.float64(-5.020892363246333e-10), np.float64(-5.123714078805714e-10), np.float64(-5.226535794365095e-10), np.float64(-5.329357509924477e-10), np.float64(-5.432179225483858e-10)]}
Predicciones para At con ventana 7: {0.5: [np.float64(-4.322978170723535), np.float64(-4.3229781707211155), np.float64(-4.322978170718696), np.float64(-4.322978170716277), np.float64(-4.322978170713857), np.float64(-4.322978170711438), np.float64(-4.3229781707090185), np.float64(-4.322978170706599), np.float64(-4.32297817070418), np.float64(-4.32297817070176), np.float64(-4.322978170699341), np.float64(-4.3229781706969215), np.float64(-4.322978170694502), np.float64(-4.322978170692083), np.float64(-4.322978170689663), np.float64(-4.322978170687244), np.float64(-4.3229781706848245), np.float64(-4.322978170682405), np.float64(-4.322978170679986), np.float64(-4.322978170677566), np.float64(-4.322978170675147), np.float64(-4.3229781706727275), np.float64(-4.322978170670308), np.float64(-4.322978170667889), np.float64(-4.322978170665469), np.float64(-4.32297817066305), np.float64(-4.3229781706606305), np.float64(-4.322978170658211)]}
Predicciones para At con ventana 14: {0.5: [np.float64(-4.322978170723535), np.float64(-4.3229781707211155), np.float64(-4.322978170718696), np.float64(-4.322978170716277), np.float64(-4.322978170713857), np.float64(-4.322978170711438), np.float64(-4.3229781707090185), np.float64(-4.322978170706599), np.float64(-4.32297817070418), np.float64(-4.32297817070176), np.float64(-4.322978170699341), np.float64(-4.3229781706969215), np.float64(-4.322978170694502), np.float64(-4.322978170692083), np.float64(-4.322978170689663), np.float64(-4.322978170687244), np.float64(-4.3229781706848245), np.float64(-4.322978170682405), np.float64(-4.322978170679986), np.float64(-4.322978170677566), np.float64(-4.322978170675147), np.float64(-4.3229781706727275), np.float64(-4.322978170670308), np.float64(-4.322978170667889), np.float64(-4.322978170665469), np.float64(-4.32297817066305), np.float64(-4.3229781706606305), np.float64(-4.322978170658211)]}
Predicciones para At con ventana 21: {0.5: [np.float64(-4.322978170723535), np.float64(-4.3229781707211155), np.float64(-4.322978170718696), np.float64(-4.322978170716277), np.float64(-4.322978170713857), np.float64(-4.322978170711438), np.float64(-4.3229781707090185), np.float64(-4.322978170706599), np.float64(-4.32297817070418), np.float64(-4.32297817070176), np.float64(-4.322978170699341), np.float64(-4.3229781706969215), np.float64(-4.322978170694502), np.float64(-4.322978170692083), np.float64(-4.322978170689663), np.float64(-4.322978170687244), np.float64(-4.3229781706848245), np.float64(-4.322978170682405), np.float64(-4.322978170679986), np.float64(-4.322978170677566), np.float64(-4.322978170675147), np.float64(-4.3229781706727275), np.float64(-4.322978170670308), np.float64(-4.322978170667889), np.float64(-4.322978170665469), np.float64(-4.32297817066305), np.float64(-4.3229781706606305), np.float64(-4.322978170658211)]}
Predicciones para At con ventana 28: {0.5: [np.float64(-4.322978170723535), np.float64(-4.3229781707211155), np.float64(-4.322978170718696), np.float64(-4.322978170716277), np.float64(-4.322978170713857), np.float64(-4.322978170711438), np.float64(-4.3229781707090185), np.float64(-4.322978170706599), np.float64(-4.32297817070418), np.float64(-4.32297817070176), np.float64(-4.322978170699341), np.float64(-4.3229781706969215), np.float64(-4.322978170694502), np.float64(-4.322978170692083), np.float64(-4.322978170689663), np.float64(-4.322978170687244), np.float64(-4.3229781706848245), np.float64(-4.322978170682405), np.float64(-4.322978170679986), np.float64(-4.322978170677566), np.float64(-4.322978170675147), np.float64(-4.3229781706727275), np.float64(-4.322978170670308), np.float64(-4.322978170667889), np.float64(-4.322978170665469), np.float64(-4.32297817066305), np.float64(-4.3229781706606305), np.float64(-4.322978170658211)]}
import numpy as np
import pandas as pd
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
# Función para calcular el MAPE
def MAPE(y_true, y_pred):
return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
# Función para realizar predicciones con Double Exponential Smoothing
def double_exponential_smoothing(series, alpha, beta, n_forecast):
level = series[0]
trend = series[1] - series[0] # Inicializar tendencia
result = [level + trend] # Primer pronóstico
# Suavizado
for t in range(1, len(series)):
last_level = level
level = alpha * series[t] + (1 - alpha) * (last_level + trend)
trend = beta * (level - last_level) + (1 - beta) * trend
# Pronóstico
if t >= len(series) - 1:
forecast = level + trend
result.append(forecast)
# Pronósticos para los próximos n períodos
forecasts = []
for i in range(n_forecast):
forecast = level + (i + 1) * trend
forecasts.append(forecast)
return forecasts
# Función para evaluar una variable con D.E.S.
def evaluar_variable_de(variable):
ventanas = [7, 14, 21, 28]
alphas = [0.5]
betas = [0.5] # También puedes probar diferentes valores para beta
train_size = len(df) - 28
test_size = 28
resultados = []
for ventana in ventanas:
for alpha in alphas:
for beta in betas:
# Serie de tiempo de entrenamiento sin NaNs
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
# Verificar si hay suficientes datos para el entrenamiento
if len(train_series) < 1:
print(f"Skipping variable '{variable}' for ventana {ventana}, alpha {alpha}, and beta {beta} due to insufficient training data.")
continue
# Obtener las predicciones para el conjunto de validación
pred_val = double_exponential_smoothing(train_series.values, alpha, beta, test_size)
# Conjunto de validación real (últimos 28 valores de la serie) sin NaNs
val_series = df[variable].iloc[-test_size:].dropna().reset_index(drop=True)
# Verificar si hay suficientes datos para la validación
if len(val_series) < test_size:
print(f"Skipping variable '{variable}' for ventana {ventana}, alpha {alpha}, and beta {beta} due to insufficient validation data.")
continue
# Calcular las métricas
mae = mean_absolute_error(val_series, pred_val)
mse = mean_squared_error(val_series, pred_val)
rmse = np.sqrt(mse)
mape = MAPE(val_series, pred_val)
r2 = r2_score(val_series, pred_val)
# Guardar los resultados en una lista
resultados.append({
'Variable': variable,
'Ventana': ventana,
'Alpha': alpha,
'Beta': beta,
'MAE': mae,
'MSE': mse,
'RMSE': rmse,
'MAPE': mape,
'R2': r2
})
return pd.DataFrame(resultados)
# Evaluar cada variable y combinar los resultados
resultados_totales_de = pd.concat([evaluar_variable_de('At'),
evaluar_variable_de('Price'),
evaluar_variable_de('Volatilidad_14')],
ignore_index=True)
# Mostrar la tabla con los resultados totales
print(resultados_totales_de)
Variable Ventana Alpha Beta MAE MSE \
0 At 7 0.5 0.5 3.007271e-11 1.272532e-21
1 At 14 0.5 0.5 3.007271e-11 1.272532e-21
2 At 21 0.5 0.5 3.007271e-11 1.272532e-21
3 At 28 0.5 0.5 3.007271e-11 1.272532e-21
4 Price 7 0.5 0.5 5.794263e-12 4.737237e-23
5 Price 14 0.5 0.5 5.794263e-12 4.737237e-23
6 Price 21 0.5 0.5 5.794263e-12 4.737237e-23
7 Price 28 0.5 0.5 5.794263e-12 4.737237e-23
8 Volatilidad_14 7 0.5 0.5 4.044086e-10 1.704448e-19
9 Volatilidad_14 14 0.5 0.5 4.044086e-10 1.704448e-19
10 Volatilidad_14 21 0.5 0.5 4.044086e-10 1.704448e-19
11 Volatilidad_14 28 0.5 0.5 4.044086e-10 1.704448e-19
RMSE MAPE R2
0 3.567257e-11 6.956479e-10 0.000000e+00
1 3.567257e-11 6.956479e-10 0.000000e+00
2 3.567257e-11 6.956479e-10 0.000000e+00
3 3.567257e-11 6.956479e-10 0.000000e+00
4 6.882759e-12 5.794263e-09 -2.459714e+11
5 6.882759e-12 5.794263e-09 -2.459714e+11
6 6.882759e-12 5.794263e-09 -2.459714e+11
7 6.882759e-12 5.794263e-09 -2.459714e+11
8 4.128495e-10 inf 0.000000e+00
9 4.128495e-10 inf 0.000000e+00
10 4.128495e-10 inf 0.000000e+00
11 4.128495e-10 inf 0.000000e+00
Variable |
Ventana |
Alpha |
Beta |
MAE |
MSE |
RMSE |
MAPE |
R² |
|---|---|---|---|---|---|---|---|---|
At |
7 |
0.5 |
0.5 |
3.007271e-11 |
1.272532e-21 |
3.567257e-11 |
6.956479e-10 |
0.000000e+00 |
Price |
7 |
0.5 |
0.5 |
5.794263e-12 |
4.737237e-23 |
6.882759e-12 |
5.794263e-09 |
-2.459714e+11 |
Volatilidad_14 |
7 |
0.5 |
0.5 |
4.044086e-10 |
1.704448e-19 |
4.128495e-10 |
inf |
0.000000e+00 |
Explicación de la Selección de una Sola Ventana#
Se ha seleccionado una única ventana de 7 para presentar los resultados del modelo de doble suavizado exponencial. Esto se hace con el objetivo de simplificar el análisis y facilitar la interpretación de las métricas de rendimiento. Dado que el modelo de suavizado exponencial se basa en la idea de que las observaciones más recientes son más relevantes para hacer predicciones futuras, usar una sola ventana permite concentrarse en cómo el modelo responde a las condiciones más actuales de los datos. Además, al fijar los parámetros Alpha y Beta en 0.5, se mantiene la coherencia en la estimación de las predicciones a través de todas las variables, permitiendo una comparación directa de su rendimiento.
Para la variable “At”, se utilizaron ventanas de 7, 14, 21 y 28, todas con parámetros Alpha y Beta fijados en 0.5. Los valores de MAE (3.007271e-11) y MSE (1.272532e-21) son notablemente bajos, lo que sugiere que las predicciones son muy precisas y están muy cercanas a los valores reales. Esto se refleja también en el RMSE (3.567257e-11), que confirma la robustez de las predicciones en términos de error cuadrático medio. Sin embargo, el MAPE es también bajo (6.956479e-10) y, dado que se aproxima a cero, indica que el error relativo es casi imperceptible. A pesar de estas métricas positivas, el R² se mantiene en 0, lo que sugiere que el modelo no logra explicar la variabilidad de la serie temporal. Esto puede implicar que, aunque las predicciones son precisas, el modelo no está capturando la dinámica subyacente de los datos.
Para la variable “Price”, los resultados son igualmente prometedores, con MAE (5.794263e-12) y MSE (4.737237e-23) que indican un ajuste excepcionalmente preciso. Los valores de RMSE (6.882759e-12) y MAPE (5.794263e-09) son también bajos, pero el R² es -2.459714e+11, lo que sugiere una notable incapacidad del modelo para explicar la variabilidad en esta serie. Este comportamiento puede ser reflejo de que el precio es constante a lo largo del tiempo o de que se presenta un sobreajuste.
Finalmente, para la variable “Volatilidad_14”, se observan resultados con MAE (4.044086e-10) y MSE (1.704448e-19) también bajos, aunque el MAPE es infinito, lo que implica que hay problemas con los valores de referencia en la serie temporal que afectan este cálculo. Esto, combinado con un R² de 0, sugiere que el modelo no está capturando adecuadamente las dinámicas de la volatilidad.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from statsmodels.stats.diagnostic import acorr_ljungbox
from statsmodels.stats.stattools import jarque_bera
# Función para calcular el MAPE
def MAPE(y_true, y_pred):
return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
# Función para realizar predicciones con Suavizado Exponencial Doble
def double_exponential_smoothing(series, alpha, beta, n_forecast):
n = len(series)
result = np.zeros(n + n_forecast) # Almacena niveles y predicciones
level = series[0]
trend = series[1] - series[0] # Inicializar la tendencia
result[0] = level
for t in range(1, n):
last_level = level
level = alpha * series[t] + (1 - alpha) * (last_level + trend)
trend = beta * (level - last_level) + (1 - beta) * trend
result[t] = level
# Predicciones futuras
for i in range(n_forecast):
result[n + i] = level + (i + 1) * trend
return result[n:], result[:n] # Retornar solo las predicciones y los niveles
# Función para evaluar la variable y analizar residuos
def evaluar_variable_con_residuos_de(variable):
alphas = [0.5]
betas = [0.5] # Puedes probar diferentes valores para beta
train_size = len(df) - 28
test_size = 28
resultados = []
for alpha in alphas:
for beta in betas:
# Serie de tiempo de entrenamiento sin NaNs
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
# Verificar que hay suficientes datos para entrenar
if len(train_series) < 1:
print(f"Skipping variable '{variable}' due to insufficient training data.")
continue
# Obtener las predicciones y niveles para el conjunto de validación
pred_val, niveles = double_exponential_smoothing(train_series.values, alpha, beta, test_size)
# Conjunto de validación real (últimos 28 valores de la serie) sin NaNs
val_series = df[variable].iloc[-test_size:].dropna().reset_index(drop=True)
# Verificar que hay suficientes datos para la validación
if len(val_series) < test_size:
print(f"Skipping variable '{variable}' due to insufficient validation data.")
continue
# Calcular métricas
mae = mean_absolute_error(val_series, pred_val)
mse = mean_squared_error(val_series, pred_val)
rmse = np.sqrt(mse)
mape = MAPE(val_series, pred_val)
r2 = r2_score(val_series, pred_val)
# Calcular residuos
residuos = val_series.values - pred_val
# Verificar la longitud de residuos
if len(residuos) < 2: # Necesitamos al menos 2 puntos para hacer análisis
print(f"Not enough data points in residues for variable '{variable}'. Skipping analysis.")
continue
# Pruebas de hipótesis
ljung_box_results = acorr_ljungbox(residuos, lags=[10], return_df=True)
ljung_box_pvalue = ljung_box_results['lb_pvalue'].values[0] if not ljung_box_results.empty else np.nan
jarque_bera_results = jarque_bera(residuos)
jarque_bera_pvalue = jarque_bera_results[1]
# Guardar resultados
resultados.append({
'Variable': variable,
'MAPE': mape,
'MAE': mae,
'RMSE': rmse,
'MSE': mse,
'R2': r2,
'Ljung-Box (p-value)': ljung_box_pvalue,
'Jarque-Bera (p-value)': jarque_bera_pvalue
})
# Gráficos
plt.figure(figsize=(15, 5))
# Serie de residuos
plt.subplot(1, 3, 1)
plt.plot(residuos)
plt.title(f'Serie de residuos: {variable}')
plt.xlabel('Tiempo')
plt.ylabel('Residuos')
# QQPlot
plt.subplot(1, 3, 2)
sm.qqplot(residuos, line='s', ax=plt.gca())
plt.title('QQPlot de residuos')
# ACF solo si hay suficientes residuos
if len(residuos) >= 30:
plt.subplot(1, 3, 3)
from statsmodels.graphics.tsaplots import plot_acf
plot_acf(residuos, lags=30, ax=plt.gca())
plt.title('ACF de residuos')
else:
plt.subplot(1, 3, 3)
plt.text(0.5, 0.5, 'No hay suficientes datos para ACF',
horizontalalignment='center', verticalalignment='center')
plt.title('ACF de residuos')
plt.tight_layout()
plt.show()
return pd.DataFrame(resultados)
# Evaluar cada variable y combinar los resultados
resultados_totales_de = pd.concat([evaluar_variable_con_residuos_de('At'),
evaluar_variable_con_residuos_de('Price'),
evaluar_variable_con_residuos_de('Volatilidad_14')],
ignore_index=True)
# Mostrar la tabla con los resultados totales
print(resultados_totales_de)
Variable MAPE MAE RMSE MSE \
0 At 6.956479e-10 3.007271e-11 3.567257e-11 1.272532e-21
1 Price 5.794263e-09 5.794263e-12 6.882759e-12 4.737237e-23
2 Volatilidad_14 inf 4.044086e-10 4.128495e-10 1.704448e-19
R2 Ljung-Box (p-value) Jarque-Bera (p-value)
0 0.000000e+00 3.769957e-15 0.42986
1 -2.459714e+11 3.769963e-15 0.42986
2 0.000000e+00 3.769957e-15 0.42986
Variable |
MAPE |
MAE |
RMSE |
MSE |
R² |
Ljung-Box (p-value) |
Jarque-Bera (p-value) |
|---|---|---|---|---|---|---|---|
At |
6.96e-10 |
3.01e-11 |
3.57e-11 |
1.27e-21 |
0.0 |
3.77e-15 |
0.42986 |
Price |
5.79e-09 |
5.79e-12 |
6.88e-12 |
4.74e-23 |
-2.46e+11 |
3.77e-15 |
0.42986 |
Volatilidad_14 |
inf |
4.04e-10 |
4.13e-10 |
1.70e-19 |
0.0 |
3.77e-15 |
0.42986 |
Los valores de MAPE son extremadamente bajos para las variables At y Price, lo que indica un ajuste muy preciso en términos relativos. Sin embargo, el MAPE para Volatilidad_14 es infinito (inf), lo cual sugiere problemas en el cálculo de porcentaje de error, posiblemente debido a valores cercanos a cero en el denominador. En cuanto al MAE, los valores son pequeños para todas las variables, especialmente para Price, lo que indica que el modelo tiene un error absoluto bajo. Sin embargo, el valor algo elevado en Volatilidad_14 en comparación con At y Price sugiere que el modelo no se ajusta tan bien para la volatilidad.
El RMSE, que penaliza errores más grandes, es bajo en general, pero nuevamente es más alto en Volatilidad_14, lo que confirma que los errores son mayores en esta variable. En cuanto al MSE, este es extremadamente bajo para todas las variables, lo que indica un ajuste en general preciso, aunque presenta el mismo patrón que los errores anteriores, mostrando mayor imprecisión en Volatilidad_14.
Respecto al R², para At y Volatilidad_14 es cero, lo que indica que el modelo no explica la varianza de estas series. Para Price, el valor negativo (-2.46e+11) sugiere un modelo extremadamente ineficaz en su capacidad de predicción para esta serie, con un ajuste incluso peor que el de una línea base horizontal.
Analisis de los graficos#
La gráfica de residuos en el tiempo muestra una tendencia descendente clara en los residuos, lo cual indica que el modelo de doble suavizado exponencial no ha capturado adecuadamente el patrón de las series de tiempo, incluyendo las variables de precio, volatilidad y retornos acumulados. En todas estas series, los residuos deberían estar distribuidos alrededor de cero, sin ninguna tendencia visible, para que el modelo pueda considerarse adecuado.
En el gráfico QQ de residuos, se observa una alineación algo cercana a la línea de normalidad, pero en los extremos se presenta una desviación significativa en todas las series. Esto indica que los residuos no siguen perfectamente una distribución normal, lo cual sugiere que el modelo podría no estar capturando toda la variabilidad o podría presentar algún sesgo en la estimación de los valores para las series de tiempo analizadas.
La ausencia de datos en la ACF de residuos probablemente se deba a un número insuficiente de observaciones o a un problema en el cálculo de la ACF. Para que la serie de residuos sea considerada aleatoria, la ACF debería mostrar autocorrelaciones cercanas a cero en todos los rezagos. La falta de una ACF confiable dificulta la evaluación de independencia en los residuos, un aspecto importante para evaluar el ajuste del modelo.
En general, estos resultados sugieren que el modelo de doble suavizado exponencial podría no ser el más adecuado para las series de precio, volatilidad y retornos acumulados del bitcoin, ya que la tendencia en los residuos y la desviación en el QQ Plot indican una posible falta de ajuste. Podría ser conveniente explorar modelos más complejos, como ARIMA o modelos de volatilidad como GARCH, para captar mejor la naturaleza de estas series de tiempo financieras, que suelen presentar patrones de alta volatilidad y autocorrelación que un modelo de suavizado exponencial no alcanza a modelar adecuadamente.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from statsmodels.tsa.holtwinters import Holt
from statsmodels.stats.diagnostic import acorr_ljungbox
# Función para calcular el MAPE
def MAPE(y_true, y_pred):
return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
# Función para realizar predicciones con Double Exponential Smoothing
def des_predict(serie, alpha, beta, test_size):
model = Holt(serie)
model_fit = model.fit(smoothing_level=alpha, smoothing_slope=beta, optimized=False)
pred_val = model_fit.forecast(test_size)
return pred_val, model_fit.resid # Retornar también los residuos
# Función para evaluar el modelo en el conjunto de prueba
def evaluar_modelo_en_test(variable):
alphas = [0.5]
betas = [0.3] # Puedes ajustar este valor
train_size = len(df) - 28 # Tamaño del conjunto de entrenamiento
test_size = 28 # Tamaño del conjunto de prueba
resultados = []
for alpha in alphas:
for beta in betas:
# Serie de tiempo de entrenamiento sin NaNs
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
# Obtener las predicciones y residuos para el conjunto de prueba
pred_val, residuos = des_predict(train_series, alpha, beta, test_size)
# Conjunto de prueba real (últimos 28 valores de la serie) sin NaNs
val_series = df[variable].iloc[-test_size:].dropna().reset_index(drop=True)
# Calcular métricas
mae = mean_absolute_error(val_series, pred_val)
mse = mean_squared_error(val_series, pred_val)
rmse = np.sqrt(mse)
mape = MAPE(val_series, pred_val)
r2 = r2_score(val_series, pred_val)
# Pruebas de hipótesis
ljung_box_results = acorr_ljungbox(residuos, lags=[10], return_df=True)
ljung_box_pvalue = ljung_box_results['lb_pvalue'].values[0] if not ljung_box_results.empty else np.nan
# Guardar resultados
resultados.append({
'Variable': variable,
'MAPE': mape,
'MAE': mae,
'RMSE': rmse,
'MSE': mse,
'R2': r2,
'Ljung-Box (p-value)': ljung_box_pvalue
})
return pd.DataFrame(resultados)
# Evaluar cada variable y combinar los resultados
resultados_totales_test = pd.concat([evaluar_modelo_en_test('At'),
evaluar_modelo_en_test('Price'),
evaluar_modelo_en_test('Volatilidad_14')],
ignore_index=True)
# Mostrar la tabla con los resultados totales del conjunto de prueba
print(resultados_totales_test)
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_11704\3045378745.py:15: FutureWarning:
the 'smoothing_slope' keyword is deprecated, use 'smoothing_trend' instead.
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_11704\3045378745.py:15: FutureWarning:
the 'smoothing_slope' keyword is deprecated, use 'smoothing_trend' instead.
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_11704\3045378745.py:15: FutureWarning:
the 'smoothing_slope' keyword is deprecated, use 'smoothing_trend' instead.
Variable MAPE MAE RMSE MSE \
0 At NaN 2.286879e-11 2.428319e-11 5.896734e-22
1 Price NaN 4.483255e-12 4.759638e-12 2.265415e-23
2 Volatilidad_14 NaN 2.715903e-09 3.040632e-09 9.245445e-18
R2 Ljung-Box (p-value)
0 0.000000e+00 1.531841e-226
1 -1.176271e+11 1.092221e-284
2 0.000000e+00 0.000000e+00
Variable |
MAPE |
MAE |
RMSE |
MSE |
R² |
Ljung-Box (p-value) |
|---|---|---|---|---|---|---|
At |
NaN |
2.286879e-11 |
2.428319e-11 |
5.896734e-22 |
0.000000e+00 |
1.531841e-226 |
Price |
NaN |
4.483255e-12 |
4.759638e-12 |
2.265415e-23 |
-1.176271e+11 |
1.092221e-284 |
Volatilidad_14 |
NaN |
2.715903e-09 |
3.040632e-09 |
9.245445e-18 |
0.000000e+00 |
0.000000e+00 |
Los resultados de los residuos de la predicción del modelo de suavizado exponencial doble muestran que el MAPE no está disponible (NaN) para todas las variables, lo cual sugiere que el cálculo del porcentaje de error relativo no es aplicable en este contexto. Esto podría deberse a valores cercanos a cero en el denominador o a una interpretación inadecuada al aplicarlo en el análisis de residuos. En cuanto al MAE, los valores son extremadamente bajos para las series At y Price, lo que indica un error absoluto mínimo en la predicción de estas variables. Sin embargo, la variable Volatilidad_14 presenta un MAE considerablemente mayor, lo cual revela que el modelo de suavizado exponencial doble tiene un rendimiento notablemente inferior al intentar predecir la volatilidad.
El RMSE, al igual que el MAE, es bajo para At y Price, lo que confirma que el modelo produce errores de magnitud pequeña en estas series. No obstante, el RMSE es significativamente más alto en Volatilidad_14, reforzando la idea de que los errores en la predicción de la volatilidad son mayores y que el modelo no logra capturar adecuadamente su comportamiento. Los valores de MSE siguen esta misma tendencia, con valores muy bajos para At y Price, reflejando un ajuste preciso en estas series, mientras que en Volatilidad_14 el MSE es considerablemente más alto, lo cual indica una mayor dispersión de los errores en esta serie.
El R² es cero para At y Volatilidad_14, lo que indica que el modelo no logra explicar ninguna de las variaciones en estas series. Para Price, el R² es negativo (-1.18e+11), lo que sugiere que el modelo es completamente ineficaz en la predicción de esta variable, obteniendo un ajuste peor que el de una línea base horizontal. Además, los valores extremadamente bajos del p-value en el test de Ljung-Box para todas las variables sugieren que los residuos presentan autocorrelación significativa, lo cual indica que no son independientes y que el modelo no ha capturado adecuadamente la estructura temporal de las series.
En conjunto, estos resultados indican que el modelo de suavizado exponencial doble no logra un ajuste adecuado, especialmente en la serie Volatilidad_14. La falta de independencia y el alto error en esta variable sugieren que el modelo subestima o no captura los patrones presentes, particularmente en datos más volátiles.
Double Exponential Smoothing (Usando statsmodels.tsa.holtwinters)#
import pandas as pd
import numpy as np
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from statsmodels.stats.diagnostic import acorr_ljungbox
from statsmodels.tsa.holtwinters import Holt
# Parámetros
alphas = [0.5] # Factores de suavización que queremos probar
ventanas = [7, 14, 21, 28] # Ventanas móviles para cada predicción
variables = ['Price', 'Volatilidad_14', 'At']
# Función para realizar Double Exponential Smoothing usando statsmodels
def double_exponential_smoothing(series, alpha, beta, n_forecast):
model = Holt(series)
model_fit = model.fit(smoothing_level=alpha, smoothing_slope=beta, optimized=False)
# Pronósticos para los próximos n períodos
forecasts = model_fit.forecast(n_forecast)
return forecasts, model_fit.fittedvalues # Solo devuelve pronósticos y valores ajustados
# Almacenaremos las predicciones
predicciones = {}
# Dividimos el conjunto de validación y entrenamiento
train_size = len(df) - 28
test_size = 28
# Para cada variable y cada ventana
for variable in variables:
predicciones[variable] = {}
# Tomamos el conjunto de entrenamiento para esa variable
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
for ventana in ventanas:
# Inicializar diccionario para cada ventana
predicciones[variable][ventana] = {}
for alpha in alphas:
# Definir un beta (puedes experimentar con este valor)
beta = 0.5
# Aplicar Double Exponential Smoothing
val_predictions, y_train_pred = double_exponential_smoothing(train_series.values, alpha, beta, test_size)
# Guardar las predicciones para esta ventana y alpha
predicciones[variable][ventana][alpha] = val_predictions
# Mostrar predicciones
for variable in predicciones:
for ventana in predicciones[variable]:
print(f"Predicciones para {variable} con ventana {ventana}: {predicciones[variable][ventana]}")
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:966: UserWarning:
Non-stationary starting autoregressive parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:978: UserWarning:
Non-invertible starting MA parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:966: UserWarning:
Non-stationary starting autoregressive parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:978: UserWarning:
Non-invertible starting MA parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:966: UserWarning:
Non-stationary starting autoregressive parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:978: UserWarning:
Non-invertible starting MA parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:966: UserWarning:
Non-stationary starting autoregressive parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:978: UserWarning:
Non-invertible starting MA parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:966: UserWarning:
Non-stationary starting autoregressive parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:978: UserWarning:
Non-invertible starting MA parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:966: UserWarning:
Non-stationary starting autoregressive parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:978: UserWarning:
Non-invertible starting MA parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:966: UserWarning:
Non-stationary starting autoregressive parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:978: UserWarning:
Non-invertible starting MA parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:966: UserWarning:
Non-stationary starting autoregressive parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:978: UserWarning:
Non-invertible starting MA parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:966: UserWarning:
Non-stationary starting autoregressive parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:978: UserWarning:
Non-invertible starting MA parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:966: UserWarning:
Non-stationary starting autoregressive parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:978: UserWarning:
Non-invertible starting MA parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:966: UserWarning:
Non-stationary starting autoregressive parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:978: UserWarning:
Non-invertible starting MA parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:966: UserWarning:
Non-stationary starting autoregressive parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\tsa\statespace\sarimax.py:978: UserWarning:
Non-invertible starting MA parameters found. Using zeros as starting parameters.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
Predicciones para Price con ventana 7: [np.float64(72572.82472370268), np.float64(70962.87102441574), np.float64(70180.57742488162), np.float64(70577.4622186235), np.float64(70598.07829052533), np.float64(70529.88802734358), np.float64(69765.60212929739), np.float64(69767.14627821947), np.float64(69673.12267727715), np.float64(69882.0236095976), np.float64(69932.7036312084), np.float64(69487.81940325872), np.float64(69273.84272644838), np.float64(69366.47728585098), np.float64(69477.85895750612), np.float64(69410.60733621173), np.float64(69299.16231247706), np.float64(69044.27359654884), np.float64(69021.2132675692), np.float64(69096.86562066393), np.float64(69073.02190646782), np.float64(69036.86448264269), np.float64(69003.62537522428), np.float64(69014.29828484202), np.float64(69056.69368099143), np.float64(69058.23130254625), np.float64(69063.95393017388), np.float64(68992.55837429232)]
MSE: 4851841337.202127, MAE: 69650.60242424314, R²: -2.5192200533264024e+43
Predicciones para Price con ventana 14: [np.float64(72572.82472370268), np.float64(70962.87102441574), np.float64(70180.57742488162), np.float64(70577.4622186235), np.float64(70598.07829052533), np.float64(70529.88802734358), np.float64(69765.60212929739), np.float64(69767.14627821947), np.float64(69673.12267727715), np.float64(69882.0236095976), np.float64(69932.7036312084), np.float64(69487.81940325872), np.float64(69273.84272644838), np.float64(69366.47728585098), np.float64(69477.85895750612), np.float64(69410.60733621173), np.float64(69299.16231247706), np.float64(69044.27359654884), np.float64(69021.2132675692), np.float64(69096.86562066393), np.float64(69073.02190646782), np.float64(69036.86448264269), np.float64(69003.62537522428), np.float64(69014.29828484202), np.float64(69056.69368099143), np.float64(69058.23130254625), np.float64(69063.95393017388), np.float64(68992.55837429232)]
MSE: 4851841337.202127, MAE: 69650.60242424314, R²: -2.5192200533264024e+43
Predicciones para Price con ventana 21: [np.float64(72572.82472370268), np.float64(70962.87102441574), np.float64(70180.57742488162), np.float64(70577.4622186235), np.float64(70598.07829052533), np.float64(70529.88802734358), np.float64(69765.60212929739), np.float64(69767.14627821947), np.float64(69673.12267727715), np.float64(69882.0236095976), np.float64(69932.7036312084), np.float64(69487.81940325872), np.float64(69273.84272644838), np.float64(69366.47728585098), np.float64(69477.85895750612), np.float64(69410.60733621173), np.float64(69299.16231247706), np.float64(69044.27359654884), np.float64(69021.2132675692), np.float64(69096.86562066393), np.float64(69073.02190646782), np.float64(69036.86448264269), np.float64(69003.62537522428), np.float64(69014.29828484202), np.float64(69056.69368099143), np.float64(69058.23130254625), np.float64(69063.95393017388), np.float64(68992.55837429232)]
MSE: 4851841337.202127, MAE: 69650.60242424314, R²: -2.5192200533264024e+43
Predicciones para Price con ventana 28: [np.float64(72572.82472370268), np.float64(70962.87102441574), np.float64(70180.57742488162), np.float64(70577.4622186235), np.float64(70598.07829052533), np.float64(70529.88802734358), np.float64(69765.60212929739), np.float64(69767.14627821947), np.float64(69673.12267727715), np.float64(69882.0236095976), np.float64(69932.7036312084), np.float64(69487.81940325872), np.float64(69273.84272644838), np.float64(69366.47728585098), np.float64(69477.85895750612), np.float64(69410.60733621173), np.float64(69299.16231247706), np.float64(69044.27359654884), np.float64(69021.2132675692), np.float64(69096.86562066393), np.float64(69073.02190646782), np.float64(69036.86448264269), np.float64(69003.62537522428), np.float64(69014.29828484202), np.float64(69056.69368099143), np.float64(69058.23130254625), np.float64(69063.95393017388), np.float64(68992.55837429232)]
MSE: 4851841337.202127, MAE: 69650.60242424314, R²: -2.5192200533264024e+43
Predicciones para Volatilidad_14 con ventana 7: [np.float64(0.03815179108758726), np.float64(0.037966539414794886), np.float64(0.03838090307659538), np.float64(0.03860503790874569), np.float64(0.03816473637907315), np.float64(0.03815048525747417), np.float64(0.03825393189562654), np.float64(0.03824741347796928), np.float64(0.03822607776655757), np.float64(0.03819934059330779), np.float64(0.038211676472402985), np.float64(0.03833218381350634), np.float64(0.038292914446658), np.float64(0.038273135504198384), np.float64(0.03828315968009288), np.float64(0.03827779266202768), np.float64(0.03827594164285306), np.float64(0.03828551727067711), np.float64(0.038273359529627), np.float64(0.0382742478131256), np.float64(0.03827753568760619), np.float64(0.038276679847233104), np.float64(0.03827636155359623), np.float64(0.038275069130926875), np.float64(0.03827634656130473), np.float64(0.03827620150506399), np.float64(0.03827758595859685), np.float64(0.038275861582451526)]
MSE: 0.0014639953023312916, MAE: 0.038262065268560005, R²: 0.0
Predicciones para Volatilidad_14 con ventana 14: [np.float64(0.03815179108758726), np.float64(0.037966539414794886), np.float64(0.03838090307659538), np.float64(0.03860503790874569), np.float64(0.03816473637907315), np.float64(0.03815048525747417), np.float64(0.03825393189562654), np.float64(0.03824741347796928), np.float64(0.03822607776655757), np.float64(0.03819934059330779), np.float64(0.038211676472402985), np.float64(0.03833218381350634), np.float64(0.038292914446658), np.float64(0.038273135504198384), np.float64(0.03828315968009288), np.float64(0.03827779266202768), np.float64(0.03827594164285306), np.float64(0.03828551727067711), np.float64(0.038273359529627), np.float64(0.0382742478131256), np.float64(0.03827753568760619), np.float64(0.038276679847233104), np.float64(0.03827636155359623), np.float64(0.038275069130926875), np.float64(0.03827634656130473), np.float64(0.03827620150506399), np.float64(0.03827758595859685), np.float64(0.038275861582451526)]
MSE: 0.0014639953023312916, MAE: 0.038262065268560005, R²: 0.0
Predicciones para Volatilidad_14 con ventana 21: [np.float64(0.03815179108758726), np.float64(0.037966539414794886), np.float64(0.03838090307659538), np.float64(0.03860503790874569), np.float64(0.03816473637907315), np.float64(0.03815048525747417), np.float64(0.03825393189562654), np.float64(0.03824741347796928), np.float64(0.03822607776655757), np.float64(0.03819934059330779), np.float64(0.038211676472402985), np.float64(0.03833218381350634), np.float64(0.038292914446658), np.float64(0.038273135504198384), np.float64(0.03828315968009288), np.float64(0.03827779266202768), np.float64(0.03827594164285306), np.float64(0.03828551727067711), np.float64(0.038273359529627), np.float64(0.0382742478131256), np.float64(0.03827753568760619), np.float64(0.038276679847233104), np.float64(0.03827636155359623), np.float64(0.038275069130926875), np.float64(0.03827634656130473), np.float64(0.03827620150506399), np.float64(0.03827758595859685), np.float64(0.038275861582451526)]
MSE: 0.0014639953023312916, MAE: 0.038262065268560005, R²: 0.0
Predicciones para Volatilidad_14 con ventana 28: [np.float64(0.03815179108758726), np.float64(0.037966539414794886), np.float64(0.03838090307659538), np.float64(0.03860503790874569), np.float64(0.03816473637907315), np.float64(0.03815048525747417), np.float64(0.03825393189562654), np.float64(0.03824741347796928), np.float64(0.03822607776655757), np.float64(0.03819934059330779), np.float64(0.038211676472402985), np.float64(0.03833218381350634), np.float64(0.038292914446658), np.float64(0.038273135504198384), np.float64(0.03828315968009288), np.float64(0.03827779266202768), np.float64(0.03827594164285306), np.float64(0.03828551727067711), np.float64(0.038273359529627), np.float64(0.0382742478131256), np.float64(0.03827753568760619), np.float64(0.038276679847233104), np.float64(0.03827636155359623), np.float64(0.038275069130926875), np.float64(0.03827634656130473), np.float64(0.03827620150506399), np.float64(0.03827758595859685), np.float64(0.038275861582451526)]
MSE: 0.0014639953023312916, MAE: 0.038262065268560005, R²: 0.0
Predicciones para At con ventana 7: [np.float64(0.04723382325738659), np.float64(0.02545030900326856), np.float64(0.022848817952236214), np.float64(0.04012766486861653), np.float64(0.038231434124326), np.float64(0.029019413980323257), np.float64(0.04168675610051997), np.float64(0.024846790429878137), np.float64(0.024927636316099233), np.float64(0.027156239795189097), np.float64(0.023799017736755256), np.float64(0.023770129321966958), np.float64(0.02456212363440725), np.float64(0.027211496309687326), np.float64(0.02409643138043594), np.float64(0.026718311201794825), np.float64(0.026161975378552846), np.float64(0.02697435337041726), np.float64(0.026161810650191478), np.float64(0.026910210082171024), np.float64(0.026762678900128543), np.float64(0.026653314414446544), np.float64(0.026600038444436938), np.float64(0.02659566715562109), np.float64(0.026351266415452063), np.float64(0.026351271198588936), np.float64(0.02630954749806174), np.float64(0.026330042410067866)]
MSE: 18.932862807403723, MAE: 4.351187048268252, R²: 0.0
Predicciones para At con ventana 14: [np.float64(0.04723382325738659), np.float64(0.02545030900326856), np.float64(0.022848817952236214), np.float64(0.04012766486861653), np.float64(0.038231434124326), np.float64(0.029019413980323257), np.float64(0.04168675610051997), np.float64(0.024846790429878137), np.float64(0.024927636316099233), np.float64(0.027156239795189097), np.float64(0.023799017736755256), np.float64(0.023770129321966958), np.float64(0.02456212363440725), np.float64(0.027211496309687326), np.float64(0.02409643138043594), np.float64(0.026718311201794825), np.float64(0.026161975378552846), np.float64(0.02697435337041726), np.float64(0.026161810650191478), np.float64(0.026910210082171024), np.float64(0.026762678900128543), np.float64(0.026653314414446544), np.float64(0.026600038444436938), np.float64(0.02659566715562109), np.float64(0.026351266415452063), np.float64(0.026351271198588936), np.float64(0.02630954749806174), np.float64(0.026330042410067866)]
MSE: 18.932862807403723, MAE: 4.351187048268252, R²: 0.0
Predicciones para At con ventana 21: [np.float64(0.04723382325738659), np.float64(0.02545030900326856), np.float64(0.022848817952236214), np.float64(0.04012766486861653), np.float64(0.038231434124326), np.float64(0.029019413980323257), np.float64(0.04168675610051997), np.float64(0.024846790429878137), np.float64(0.024927636316099233), np.float64(0.027156239795189097), np.float64(0.023799017736755256), np.float64(0.023770129321966958), np.float64(0.02456212363440725), np.float64(0.027211496309687326), np.float64(0.02409643138043594), np.float64(0.026718311201794825), np.float64(0.026161975378552846), np.float64(0.02697435337041726), np.float64(0.026161810650191478), np.float64(0.026910210082171024), np.float64(0.026762678900128543), np.float64(0.026653314414446544), np.float64(0.026600038444436938), np.float64(0.02659566715562109), np.float64(0.026351266415452063), np.float64(0.026351271198588936), np.float64(0.02630954749806174), np.float64(0.026330042410067866)]
MSE: 18.932862807403723, MAE: 4.351187048268252, R²: 0.0
Predicciones para At con ventana 28: [np.float64(0.04723382325738659), np.float64(0.02545030900326856), np.float64(0.022848817952236214), np.float64(0.04012766486861653), np.float64(0.038231434124326), np.float64(0.029019413980323257), np.float64(0.04168675610051997), np.float64(0.024846790429878137), np.float64(0.024927636316099233), np.float64(0.027156239795189097), np.float64(0.023799017736755256), np.float64(0.023770129321966958), np.float64(0.02456212363440725), np.float64(0.027211496309687326), np.float64(0.02409643138043594), np.float64(0.026718311201794825), np.float64(0.026161975378552846), np.float64(0.02697435337041726), np.float64(0.026161810650191478), np.float64(0.026910210082171024), np.float64(0.026762678900128543), np.float64(0.026653314414446544), np.float64(0.026600038444436938), np.float64(0.02659566715562109), np.float64(0.026351266415452063), np.float64(0.026351271198588936), np.float64(0.02630954749806174), np.float64(0.026330042410067866)]
MSE: 18.932862807403723, MAE: 4.351187048268252, R²: 0.0
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\statsmodels\base\model.py:607: ConvergenceWarning:
Maximum Likelihood optimization failed to converge. Check mle_retvals
import numpy as np
import pandas as pd
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from statsmodels.tsa.holtwinters import Holt
# Función para calcular el MAPE
def MAPE(y_true, y_pred):
return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
# Función para realizar predicciones con Double Exponential Smoothing utilizando statsmodels
def double_exponential_smoothing(series, alpha, beta, n_forecast):
model = Holt(series)
model_fit = model.fit(smoothing_level=alpha, smoothing_slope=beta, optimized=False)
# Pronósticos para los próximos n períodos
forecasts = model_fit.forecast(n_forecast)
return forecasts
# Función para evaluar una variable con D.E.S.
def evaluar_variable_de(variable):
ventanas = [7, 14, 21, 28]
alphas = [0.5]
betas = [0.5] # También puedes probar diferentes valores para beta
train_size = len(df) - 28
test_size = 28
resultados = []
for ventana in ventanas:
for alpha in alphas:
for beta in betas:
# Serie de tiempo de entrenamiento sin NaNs
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
# Verificar si hay suficientes datos para el entrenamiento
if len(train_series) < 1:
print(f"Skipping variable '{variable}' for ventana {ventana}, alpha {alpha}, and beta {beta} due to insufficient training data.")
continue
# Obtener las predicciones para el conjunto de validación
pred_val = double_exponential_smoothing(train_series.values, alpha, beta, test_size)
# Conjunto de validación real (últimos 28 valores de la serie) sin NaNs
val_series = df[variable].iloc[-test_size:].dropna().reset_index(drop=True)
# Verificar si hay suficientes datos para la validación
if len(val_series) < test_size:
print(f"Skipping variable '{variable}' for ventana {ventana}, alpha {alpha}, and beta {beta} due to insufficient validation data.")
continue
# Calcular las métricas
mae = mean_absolute_error(val_series, pred_val)
mse = mean_squared_error(val_series, pred_val)
rmse = np.sqrt(mse)
mape = MAPE(val_series, pred_val)
r2 = r2_score(val_series, pred_val)
# Guardar los resultados en una lista
resultados.append({
'Variable': variable,
'Ventana': ventana,
'Alpha': alpha,
'Beta': beta,
'MAE': mae,
'MSE': mse,
'RMSE': rmse,
'MAPE': mape,
'R2': r2
})
return pd.DataFrame(resultados)
# Evaluar cada variable y combinar los resultados
resultados_totales_de = pd.concat([evaluar_variable_de('At'),
evaluar_variable_de('Price'),
evaluar_variable_de('Volatilidad_14')],
ignore_index=True)
# Mostrar la tabla con los resultados totales
print(resultados_totales_de)
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_11704\935398304.py:13: FutureWarning:
the 'smoothing_slope' keyword is deprecated, use 'smoothing_trend' instead.
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_11704\935398304.py:13: FutureWarning:
the 'smoothing_slope' keyword is deprecated, use 'smoothing_trend' instead.
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_11704\935398304.py:13: FutureWarning:
the 'smoothing_slope' keyword is deprecated, use 'smoothing_trend' instead.
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_11704\935398304.py:13: FutureWarning:
the 'smoothing_slope' keyword is deprecated, use 'smoothing_trend' instead.
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_11704\935398304.py:13: FutureWarning:
the 'smoothing_slope' keyword is deprecated, use 'smoothing_trend' instead.
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_11704\935398304.py:13: FutureWarning:
the 'smoothing_slope' keyword is deprecated, use 'smoothing_trend' instead.
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_11704\935398304.py:13: FutureWarning:
the 'smoothing_slope' keyword is deprecated, use 'smoothing_trend' instead.
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_11704\935398304.py:13: FutureWarning:
the 'smoothing_slope' keyword is deprecated, use 'smoothing_trend' instead.
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_11704\935398304.py:13: FutureWarning:
the 'smoothing_slope' keyword is deprecated, use 'smoothing_trend' instead.
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_11704\935398304.py:13: FutureWarning:
the 'smoothing_slope' keyword is deprecated, use 'smoothing_trend' instead.
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_11704\935398304.py:13: FutureWarning:
the 'smoothing_slope' keyword is deprecated, use 'smoothing_trend' instead.
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_11704\935398304.py:13: FutureWarning:
the 'smoothing_slope' keyword is deprecated, use 'smoothing_trend' instead.
Variable Ventana Alpha Beta MAE MSE \
0 At 7 0.5 0.5 3.007271e-11 1.272532e-21
1 At 14 0.5 0.5 3.007271e-11 1.272532e-21
2 At 21 0.5 0.5 3.007271e-11 1.272532e-21
3 At 28 0.5 0.5 3.007271e-11 1.272532e-21
4 Price 7 0.5 0.5 5.794263e-12 4.737237e-23
5 Price 14 0.5 0.5 5.794263e-12 4.737237e-23
6 Price 21 0.5 0.5 5.794263e-12 4.737237e-23
7 Price 28 0.5 0.5 5.794263e-12 4.737237e-23
8 Volatilidad_14 7 0.5 0.5 4.044086e-10 1.704448e-19
9 Volatilidad_14 14 0.5 0.5 4.044086e-10 1.704448e-19
10 Volatilidad_14 21 0.5 0.5 4.044086e-10 1.704448e-19
11 Volatilidad_14 28 0.5 0.5 4.044086e-10 1.704448e-19
RMSE MAPE R2
0 3.567257e-11 6.956479e-10 0.000000e+00
1 3.567257e-11 6.956479e-10 0.000000e+00
2 3.567257e-11 6.956479e-10 0.000000e+00
3 3.567257e-11 6.956479e-10 0.000000e+00
4 6.882759e-12 5.794263e-09 -2.459714e+11
5 6.882759e-12 5.794263e-09 -2.459714e+11
6 6.882759e-12 5.794263e-09 -2.459714e+11
7 6.882759e-12 5.794263e-09 -2.459714e+11
8 4.128495e-10 inf 0.000000e+00
9 4.128495e-10 inf 0.000000e+00
10 4.128495e-10 inf 0.000000e+00
11 4.128495e-10 inf 0.000000e+00
Variable |
Ventana |
Alpha |
Beta |
MAE |
MSE |
RMSE |
MAPE |
R2 |
|---|---|---|---|---|---|---|---|---|
At |
7 |
0.5 |
0.5 |
3.007271e-11 |
1.272532e-21 |
3.567257e-11 |
6.956479e-10 |
0.000000e+00 |
Price |
7 |
0.5 |
0.5 |
5.794263e-12 |
4.737237e-23 |
6.882759e-12 |
5.794263e-09 |
-2.459714e+11 |
Volatilidad_14 |
7 |
0.5 |
0.5 |
4.044086e-10 |
1.704448e-19 |
4.128495e-10 |
inf |
0.000000e+00 |
En términos de Error Medio Absoluto (MAE), los valores son notablemente bajos para las tres variables. La serie de At presenta un MAE de 3.007271 × 1 0 − 11 3.007271×10 −11 , mientras que el Precio muestra un MAE de 5.794263 × 1 0 − 12 5.794263×10 −12 . Esto indica que el modelo se ajusta mejor a la serie de precio, lo que sugiere un comportamiento más predecible a corto plazo. Por otro lado, la Volatilidad_14 tiene un MAE de 4.044086 × 1 0 − 10 4.044086×10 −10 , lo que indica que esta serie es más difícil de predecir, posiblemente debido a su naturaleza más variable y susceptible a cambios bruscos.
El Error Cuadrático Medio (MSE) también es bajo en general, con un MSE de 1.272532 × 1 0 − 21 1.272532×10 −21 para At, 4.737237 × 1 0 − 23 4.737237×10 −23 para el Precio, y 1.704448 × 1 0 − 19 1.704448×10 −19 para Volatilidad_14. Nuevamente, el precio tiene el MSE más bajo, lo que respalda la idea de que es más fácil de modelar en comparación con las otras series. Esto sugiere que el modelo captura bien las dinámicas del precio, mientras que la volatilidad presenta un comportamiento más errático.
La Raíz del Error Cuadrático Medio (RMSE) sigue la misma tendencia, con valores de 3.567257 × 1 0 − 11 3.567257×10 −11 para At, 6.882759 × 1 0 − 12 6.882759×10 −12 para el Precio, y 4.128495 × 1 0 − 10 4.128495×10 −10 para Volatilidad_14. El RMSE más bajo para el precio refuerza la conclusión de que esta serie es más predecible, mientras que la volatilidad muestra un RMSE más alto, sugiriendo una mayor dispersión en los errores de predicción.
Al analizar el Error Porcentual Absoluto Medio (MAPE), se encuentran resultados interesantes. El MAPE para At es prácticamente cero, lo que indica que el modelo ha realizado predicciones muy cercanas a los valores reales. Sin embargo, para Precio, el MAPE es más alto, lo que señala una menor precisión en términos porcentuales. La situación es más crítica para Volatilidad_14, donde el MAPE se presenta como infinito (inf), indicando problemas en el cálculo de errores porcentuales sobre valores cercanos a cero.
Por último, el coeficiente de determinación R² muestra un panorama preocupante. Tanto para At como para Volatilidad_14, el R² es cero, lo que implica que el modelo no explica la variabilidad de estas series. Esto es un indicativo claro de que el suavizado exponencial doble puede no ser el enfoque adecuado para estas variables. Para el Precio, el R² negativo de − 2.459714 × 1 0 11 −2.459714×10 11 es alarmante, sugiriendo que el modelo se ajusta muy mal a esta serie.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.holtwinters import ExponentialSmoothing
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from statsmodels.graphics.gofplots import qqplot
from statsmodels.stats.diagnostic import acorr_ljungbox
from statsmodels.graphics.tsaplots import plot_acf
from scipy.stats import jarque_bera
# Función para calcular el MAPE
def MAPE(y_true, y_pred):
return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
# Función para realizar el ajuste y la evaluación del modelo Double Exponential Smoothing
def evaluar_modelo_residuos(variable):
ventanas = [7, 14, 21, 28]
alphas = [0.5]
betas = [0.5] # También puedes probar diferentes valores para beta
train_size = len(df) - 28
test_size = 28
resultados = []
for ventana in ventanas:
for alpha in alphas:
for beta in betas:
# Serie de tiempo de entrenamiento sin NaNs
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
# Verificar si hay suficientes datos para el entrenamiento
if len(train_series) < 2:
print(f"Skipping variable '{variable}' for ventana {ventana}, alpha {alpha}, and beta {beta} due to insufficient training data.")
continue
# Ajustar el modelo Double Exponential Smoothing
model = ExponentialSmoothing(train_series, trend="add", damped_trend=False).fit(smoothing_level=alpha, smoothing_trend=beta)
# Predicciones en el conjunto de entrenamiento
fitted_values = model.fittedvalues
# Calcular los residuos (observados - predichos)
residuos = train_series.values - fitted_values
# Calcular métricas de error
mae = mean_absolute_error(train_series, fitted_values)
mse = mean_squared_error(train_series, fitted_values)
rmse = np.sqrt(mse)
mape = MAPE(train_series, fitted_values)
r2 = r2_score(train_series, fitted_values)
# Prueba de independencia (Ljung-Box)
ljung_box_results = acorr_ljungbox(residuos, lags=[10], return_df=True)
ljung_box_pvalue = ljung_box_results['lb_pvalue'].values[0] if not ljung_box_results.empty else np.nan
# Prueba de normalidad (Jarque-Bera) desde scipy
jarque_bera_stat, jarque_bera_pvalue = jarque_bera(residuos)
# Guardar resultados
resultados.append({
'Variable': variable,
'Ventana': ventana,
'Alpha': alpha,
'Beta': beta,
'MAPE': mape,
'MAE': mae,
'MSE': mse,
'RMSE': rmse,
'R2': r2,
'LJung-Box (p-value)': ljung_box_pvalue,
'Jarque-Bera (p-value)': jarque_bera_pvalue
})
# Visualización
plt.figure(figsize=(12, 8))
# Serie de residuos
plt.subplot(3, 1, 1)
plt.plot(residuos, label="Residuos")
plt.axhline(0, color='red', linestyle='--', lw=2)
plt.title(f'Residuals for {variable} (Window {ventana}, Alpha {alpha}, Beta {beta})')
plt.legend()
# QQPlot para residuos
plt.subplot(3, 1, 2)
qqplot(residuos, line='s', ax=plt.gca())
plt.title('QQPlot de Residuos')
# ACF de residuos, asegurando que haya suficientes datos
if len(residuos) > 1:
plt.subplot(3, 1, 3)
plot_acf(residuos, ax=plt.gca())
plt.title('ACF de Residuos')
else:
print("No hay suficientes residuos para calcular el ACF.")
plt.tight_layout()
plt.show()
return pd.DataFrame(resultados)
# Evaluar los residuos para las tres variables de interés
resultados_residuos_at = evaluar_modelo_residuos('At')
resultados_residuos_price = evaluar_modelo_residuos('Price')
resultados_residuos_volatilidad = evaluar_modelo_residuos('Volatilidad_14')
# Combinar los resultados de las tres variables en una sola tabla
resultados_totales_residuos = pd.concat([resultados_residuos_at,
resultados_residuos_price,
resultados_residuos_volatilidad],
ignore_index=True)
# Mostrar la tabla con los resultados combinados
print(resultados_totales_residuos)
Variable Ventana Alpha Beta MAPE MAE \
0 At 7 0.5 0.5 11.766277 0.039697
1 At 14 0.5 0.5 11.766277 0.039697
2 At 21 0.5 0.5 11.766277 0.039697
3 At 28 0.5 0.5 11.766277 0.039697
4 Price 7 0.5 0.5 4.040196 344.475492
5 Price 14 0.5 0.5 4.040196 344.475492
6 Price 21 0.5 0.5 4.040196 344.475492
7 Price 28 0.5 0.5 4.040196 344.475492
8 Volatilidad_14 7 0.5 0.5 inf 0.004671
9 Volatilidad_14 14 0.5 0.5 inf 0.004671
10 Volatilidad_14 21 0.5 0.5 inf 0.004671
11 Volatilidad_14 28 0.5 0.5 inf 0.004671
MSE RMSE R2 LJung-Box (p-value) \
0 0.005099 0.071409 0.996973 1.106629e-246
1 0.005099 0.071409 0.996973 1.106629e-246
2 0.005099 0.071409 0.996973 1.106629e-246
3 0.005099 0.071409 0.996973 1.106629e-246
4 641546.226847 800.965809 0.997467 5.033189e-284
5 641546.226847 800.965809 0.997467 5.033189e-284
6 641546.226847 800.965809 0.997467 5.033189e-284
7 641546.226847 800.965809 0.997467 5.033189e-284
8 0.000109 0.010464 0.942980 2.625957e-284
9 0.000109 0.010464 0.942980 2.625957e-284
10 0.000109 0.010464 0.942980 2.625957e-284
11 0.000109 0.010464 0.942980 2.625957e-284
Jarque-Bera (p-value)
0 0.0
1 0.0
2 0.0
3 0.0
4 0.0
5 0.0
6 0.0
7 0.0
8 0.0
9 0.0
10 0.0
11 0.0
Variable |
Ventana |
Alpha |
Beta |
MAPE |
MAE |
MSE |
RMSE |
R2 |
LJung-Box (p-value) |
|---|---|---|---|---|---|---|---|---|---|
At |
7 |
0.5 |
0.5 |
11.766277 |
0.039697 |
0.005099 |
0.071409 |
0.996973 |
1.106629e-246 |
Price |
7 |
0.5 |
0.5 |
4.040196 |
344.475492 |
641546.226847 |
800.965809 |
0.997467 |
5.033189e-284 |
Volatilidad_14 |
7 |
0.5 |
0.5 |
inf |
0.004671 |
0.000109 |
0 |
Al analizar los resultados obtenidos para los residuos del entrenamiento del modelo de suavizado exponencial doble utilizando la librería Statsmodels, se pueden extraer conclusiones importantes. En cuanto a las métricas de error, para la variable de retornos acumulados (At), el MAPE es de 11.77 y el MAE de 0.0397, lo que indica un nivel de error razonable en las predicciones. La MSE de 0.0051 y el RMSE de 0.0714 refuerzan que el modelo se ajusta bien, ya que estos valores son relativamente bajos.
Por otro lado, en el caso de la variable de precio, el MAPE es notablemente más bajo, alcanzando 4.04, pero el MAE llega a 344.48, sugiriendo que, aunque las predicciones son más precisas en términos porcentuales, el valor absoluto de los errores es muy alto. Esto se ve reflejado en una MSE de 641546.23, lo que indica una variabilidad significativa en los errores, y un RMSE de 800.97, que es bastante alto. Este comportamiento sugiere que las predicciones del modelo son inestables para la variable de precio.
Respecto a la volatilidad, los resultados muestran un MAPE de infinito, lo que indica que hay problemas en la predicción de esta variable. Sin embargo, el MAE es bajo, alcanzando solo 0.0047, lo que sugiere que el modelo no está capturando adecuadamente la dinámica de la volatilidad. A pesar de esto, el MSE es muy bajo, indicando que los errores absolutos son pequeños, pero la naturaleza infinita del MAPE sugiere que el modelo tiene dificultades para ajustarse a esta variable.
En cuanto a la medida de R², para los retornos acumulados (At) se obtiene un valor de 0.997, lo que indica que el modelo explica casi el 99.7% de la variabilidad en los datos. Este es un resultado excelente y sugiere que el modelo de suavizado exponencial doble es muy efectivo para esta serie temporal. Por su parte, el R² para el precio es de 0.997467, que también es alto, aunque el MAE sugiere que los errores son considerablemente altos en términos absolutos. Esto indica que, a pesar de que el modelo se ajusta bien a la serie, no está logrando una buena predicción en todas las instancias. En el caso de la volatilidad, el R² es de 0.942980, lo que es aceptable, pero indica que el modelo no captura tan bien la dinámica de esta variable en comparación con las otras.
Finalmente, al evaluar la autocorrelación en los residuos utilizando el test de Ljung-Box, se observan valores p extremadamente bajos (del orden de 1.106629e-246 para los retornos acumulados y 5.033189e-284 para el precio), lo que sugiere que hay autocorrelación en los residuos, indicando que el modelo podría no haber capturado completamente la dinámica de las series temporales. Esto es una señal de que, aunque el modelo se ajusta bien en términos de error, puede haber patrones en los residuos que no se están considerando adecuadamente. Para la volatilidad, el p-value también es bajo (2.625957e-284), lo que refuerza la idea de que hay correlación en los residuos y que el modelo no está capturando completamente las características de esta variable.
Analisis de los graficos:#
Análisis de Residuos
Gráfico de Residuos en el Tiempo:
En esta gráfica, los residuos se distribuyen mayormente alrededor de la línea de cero (marcada en rojo), indicando una mejora significativa en comparación con el primer gráfico presentado. Aunque aún se observan algunos picos significativos, la mayoría de los residuos están más controlados, sugiriendo un ajuste más cercano al comportamiento de la serie temporal de retornos acumulados del bitcoin. Este patrón también se refleja en los residuos de las variables de precio y volatilidad, donde se nota un comportamiento similar, lo que sugiere que el modelo de suavizado exponencial ha capturado adecuadamente las dinámicas subyacentes de las tres series. Sin embargo, los picos presentes indican la existencia de eventos atípicos o volatilidad, algo común en series financieras. Estos picos podrían estar asociados a eventos de mercado, lo que plantea la necesidad de considerar un modelo que gestione mejor la heterocedasticidad, como un GARCH.
QQ Plot de Residuos:
En el QQ Plot, los puntos se alinean mejor con la línea roja (distribución normal teórica) en el rango intermedio de valores, lo que sugiere que los residuos son aproximadamente normales en gran parte de los datos. Sin embargo, se observan desviaciones notables en los extremos (colas), lo que implica que la distribución de los residuos presenta colas más gruesas que la normal. Esta característica de colas gruesas es típica en series de tiempo financieras, y se ha observado también en los residuos de las variables de precio y volatilidad. Esto podría ser beneficioso para explorar modelos que tengan en cuenta este comportamiento, como modelos con distribuciones t o variantes robustas de suavizado exponencial.
ACF de Residuos:
La ACF de los residuos muestra pocos picos significativos en los primeros rezagos, pero en general, los residuos están relativamente descorrelacionados. Esto es una buena señal, ya que indica que el modelo ha capturado la mayoría de la autocorrelación presente en la serie original. Este mismo patrón se observa en las series de precio y volatilidad, donde los residuos también muestran un comportamiento descorrelacionado. Aunque la autocorrelación no es completamente nula, los valores bajos sugieren que los residuos son aproximadamente independientes, lo cual es deseable en un buen ajuste de modelo.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from statsmodels.tsa.holtwinters import Holt
from statsmodels.stats.diagnostic import acorr_ljungbox
# Función para calcular el MAPE
def MAPE(y_true, y_pred):
return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
# Función para realizar predicciones con Double Exponential Smoothing
def des_predict(serie, alpha, beta, test_size):
model = Holt(serie)
model_fit = model.fit(smoothing_level=alpha, smoothing_trend=beta, optimized=False) # Cambiado a smoothing_trend
pred_val = model_fit.forecast(test_size)
return pred_val, model_fit.resid # Retornar también los residuos
# Función para evaluar el modelo en el conjunto de prueba
def evaluar_modelo_en_test(variable):
alphas = [0.5] # Puedes ajustar el valor de alpha
betas = [0.3] # Puedes ajustar el valor de beta
train_size = len(df) - 28 # Tamaño del conjunto de entrenamiento
test_size = 28 # Tamaño del conjunto de prueba
resultados = []
for alpha in alphas:
for beta in betas:
# Serie de tiempo de entrenamiento sin NaNs
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
# Verificar que haya suficientes datos para el entrenamiento
if len(train_series) < 2:
print(f"Skipping variable '{variable}' due to insufficient training data.")
continue
# Obtener las predicciones y residuos para el conjunto de prueba
pred_val, residuos = des_predict(train_series, alpha, beta, test_size)
# Conjunto de prueba real (últimos 28 valores de la serie) sin NaNs
val_series = df[variable].iloc[-test_size:].dropna().reset_index(drop=True)
# Asegurarse de que el tamaño de las predicciones y la serie de prueba coincidan
if len(pred_val) != len(val_series):
print(f"Skipping variable '{variable}' due to mismatch in prediction and validation set lengths.")
continue
# Calcular métricas
mae = mean_absolute_error(val_series, pred_val)
mse = mean_squared_error(val_series, pred_val)
rmse = np.sqrt(mse)
mape = MAPE(val_series, pred_val)
r2 = r2_score(val_series, pred_val)
# Pruebas de hipótesis
ljung_box_results = acorr_ljungbox(residuos, lags=[10], return_df=True)
ljung_box_pvalue = ljung_box_results['lb_pvalue'].values[0] if not ljung_box_results.empty else np.nan
# Guardar resultados
resultados.append({
'Variable': variable,
'MAPE': mape,
'MAE': mae,
'RMSE': rmse,
'MSE': mse,
'R2': r2,
'Ljung-Box (p-value)': ljung_box_pvalue
})
return pd.DataFrame(resultados)
# Evaluar cada variable y combinar los resultados
resultados_totales_test = pd.concat([evaluar_modelo_en_test('At'),
evaluar_modelo_en_test('Price'),
evaluar_modelo_en_test('Volatilidad_14')],
ignore_index=True)
# Mostrar la tabla con los resultados totales del conjunto de prueba
print(resultados_totales_test)
Variable MAPE MAE RMSE MSE \
0 At NaN 2.286879e-11 2.428319e-11 5.896734e-22
1 Price NaN 4.483255e-12 4.759638e-12 2.265415e-23
2 Volatilidad_14 NaN 2.715903e-09 3.040632e-09 9.245445e-18
R2 Ljung-Box (p-value)
0 0.000000e+00 1.531841e-226
1 -1.176271e+11 1.092221e-284
2 0.000000e+00 0.000000e+00
Variable |
MAPE |
MAE |
RMSE |
MSE |
R2 |
Ljung-Box (p-value) |
|---|---|---|---|---|---|---|
At |
NaN |
2.286879e-11 |
2.428319e-11 |
5.896734e-22 |
0.000000e+00 |
1.531841e-226 |
Price |
NaN |
4.483255e-12 |
4.759638e-12 |
2.265415e-23 |
-1.176271e+11 |
1.092221e-284 |
Volatilidad_14 |
NaN |
2.715903e-09 |
3.040632e-09 |
9.245445e-18 |
0.000000e+00 |
0.000000e+00 |
Al examinar los resultados de los residuos de la predicción del modelo de suavizado exponencial doble, se observa un panorama interesante sobre el desempeño del modelo en las diferentes variables analizadas.
Para la variable de retornos acumulados (At), el MAE es de 2.286879 × 1 0 − 11 2.286879×10 −11 y el RMSE es de 2.428319 × 1 0 − 11 2.428319×10 −11 , lo que indica un nivel de error muy bajo en las predicciones. Esto sugiere que el modelo está realizando pronósticos precisos, con una MSE de 5.896734 × 1 0 − 22 5.896734×10 −22 que refuerza la calidad del ajuste. Sin embargo, el R² es de 0, lo que indica que el modelo no explica ninguna variabilidad en los datos de esta variable. Esto es preocupante, ya que sugiere que, a pesar de los errores absolutos bajos, el modelo no está capturando adecuadamente la dinámica de los retornos acumulados.
En el caso de la variable de precio, el MAE se sitúa en 4.483255 × 1 0 − 12 4.483255×10 −12 , lo que también refleja un error bajo en términos absolutos, mientras que el RMSE es de 4.759638 × 1 0 − 12 4.759638×10 −12 y la MSE de 2.265415 × 1 0 − 23 2.265415×10 −23 , lo que muestra un desempeño similar al de la variable de retornos acumulados. Sin embargo, el R² de -1.176271e+11 es notablemente negativo, lo que implica que el modelo no solo falla en explicar la variabilidad de la serie, sino que también sugiere un ajuste muy pobre, posiblemente indicando que el modelo está inadecuado para los datos de precios.
Finalmente, en lo que respecta a la volatilidad, el MAE de 2.715903 × 1 0 − 09 2.715903×10 −09 y el RMSE de 3.040632 × 1 0 − 09 3.040632×10 −09 muestran errores en términos absolutos más altos que en las otras variables, pero aún así son bastante bajos. La MSE es de 9.245445 × 1 0 − 18 9.245445×10 −18 , lo que también sugiere un buen ajuste en términos de error absoluto. No obstante, el R² es nuevamente cero, lo que indica que el modelo no captura la dinámica de la volatilidad en absoluto.
Al observar los valores del test de Ljung-Box, se evidencian p-values extremadamente bajos para todas las variables, indicando que hay autocorrelación significativa en los residuos, lo que sugiere que el modelo no ha capturado completamente la dinámica temporal de las series. Para los retornos acumulados, el p-value es de 1.531841 × 1 0 − 226 1.531841×10 −226 ; para el precio, es de 1.092221 × 1 0 − 284 1.092221×10 −284 ; y para la volatilidad, es de 0.000000 0.000000. Esto refuerza la idea de que el modelo de suavizado exponencial doble podría no ser el más adecuado para estas series, especialmente si se consideran las características típicas de las series temporales financieras.
Modelo Arima (Usando Rolling)#
import pandas as pd
import numpy as np
import warnings # Para suprimir advertencias
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from statsmodels.tsa.arima.model import ARIMA
# Suprimir advertencias de convergencia
warnings.filterwarnings("ignore", message=".*Maximum Likelihood optimization failed to converge.*")
warnings.filterwarnings("ignore", message=".*Non-stationary starting autoregressive parameters found.*")
warnings.filterwarnings("ignore", message=".*Non-invertible starting MA parameters found.*")
# Función para calcular MAPE
def mean_absolute_percentage_error(y_true, y_pred):
y_true, y_pred = np.array(y_true), np.array(y_pred)
return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
# Función para realizar ARIMA con rolling window
def arima_rolling(series, p, d, q, window_size, n_forecast):
history = list(series[:window_size]) # Inicialmente tomar los primeros 'window_size' valores como historia
predictions = []
for t in range(n_forecast):
# Ajustar ARIMA en la ventana
model = ARIMA(history, order=(p, d, q))
model_fit = model.fit()
# Predecir el siguiente valor
forecast = model_fit.forecast(steps=1)[0]
predictions.append(forecast)
# Actualizar la historia con el nuevo valor
history.append(forecast)
history.pop(0) # Limitar el tamaño de la historia al tamaño de la ventana
return predictions
# Parámetros
ventanas = [7, 14, 21, 28] # Ventanas de predicción
variables = ['Price', 'Volatilidad_14', 'At']
# Almacenaremos las predicciones y métricas
predicciones = {}
# Dividimos el conjunto de validación y entrenamiento
train_size = len(df) - 28
test_size = 28
# Para cada variable y cada ventana
for variable in variables:
predicciones[variable] = {}
# Tomamos el conjunto de entrenamiento para esa variable
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
for ventana in ventanas:
# Inicializar diccionario para cada ventana
predicciones[variable][ventana] = {}
# Definir parámetros ARIMA
p, d, q = 2, 2, 5 # Puedes ajustar estos parámetros según necesidad
# Aplicar ARIMA con rolling window de tamaño 14
val_predictions = arima_rolling(train_series.values, p, d, q, window_size=14, n_forecast=test_size)
# Guardar las predicciones para esta ventana
predicciones[variable][ventana]['predictions'] = val_predictions
# Evaluar en el conjunto de validación
y_true = df[variable].iloc[train_size:].values # Valores reales de validación
# Métricas de error
mse = mean_squared_error(y_true, val_predictions)
mae = mean_absolute_error(y_true, val_predictions)
rmse = np.sqrt(mse) # RMSE es la raíz cuadrada del MSE
r2 = r2_score(y_true, val_predictions)
mape = mean_absolute_percentage_error(y_true, val_predictions)
# Guardar las métricas en el diccionario
predicciones[variable][ventana]['mse'] = mse
predicciones[variable][ventana]['mae'] = mae
predicciones[variable][ventana]['rmse'] = rmse
predicciones[variable][ventana]['r2'] = r2
predicciones[variable][ventana]['mape'] = mape
# Mostrar los resultados del modelo actual
print(f"\nResultados para {variable} con ventana {ventana}:")
print(f"Modelo ARIMA(p={p}, d={d}, q={q})")
print(f"Predicciones: {predicciones[variable][ventana]['predictions']}")
print(f"MSE: {predicciones[variable][ventana]['mse']}")
print(f"MAE: {predicciones[variable][ventana]['mae']}")
print(f"RMSE: {predicciones[variable][ventana]['rmse']}")
print(f"R²: {predicciones[variable][ventana]['r2']}")
print(f"MAPE: {predicciones[variable][ventana]['mape']}%")
Resultados para Price con ventana 7:
Modelo ARIMA(p=2, d=2, q=5)
Predicciones: [np.float64(71936.04264106308), np.float64(71131.95142390691), np.float64(70454.81408034434), np.float64(70036.46667081754), np.float64(69616.01516889925), np.float64(69305.9955749856), np.float64(68990.24825091186), np.float64(68694.48418692414), np.float64(68419.1339571371), np.float64(68148.09069806633), np.float64(67875.27679369877), np.float64(67607.58745212312), np.float64(67322.37578719298), np.float64(67014.49838813824), np.float64(66703.50403959658), np.float64(66383.75278129114), np.float64(66079.04250538522), np.float64(65766.63847679693), np.float64(65463.025575629006), np.float64(65159.16062149808), np.float64(64854.31681890181), np.float64(64549.72161091137), np.float64(64241.546763007456), np.float64(63938.06408990121), np.float64(63632.13774770242), np.float64(63325.387826914106), np.float64(63019.863542541825), np.float64(62711.72513219054)]
MSE: 4478145596.178828
MAE: 66870.64530737417
RMSE: 66918.94796078932
R²: -2.3251861311100903e+43
MAPE: 66870645.307374164%
Resultados para Price con ventana 14:
Modelo ARIMA(p=2, d=2, q=5)
Predicciones: [np.float64(71936.04264106308), np.float64(71131.95142390691), np.float64(70454.81408034434), np.float64(70036.46667081754), np.float64(69616.01516889925), np.float64(69305.9955749856), np.float64(68990.24825091186), np.float64(68694.48418692414), np.float64(68419.1339571371), np.float64(68148.09069806633), np.float64(67875.27679369877), np.float64(67607.58745212312), np.float64(67322.37578719298), np.float64(67014.49838813824), np.float64(66703.50403959658), np.float64(66383.75278129114), np.float64(66079.04250538522), np.float64(65766.63847679693), np.float64(65463.025575629006), np.float64(65159.16062149808), np.float64(64854.31681890181), np.float64(64549.72161091137), np.float64(64241.546763007456), np.float64(63938.06408990121), np.float64(63632.13774770242), np.float64(63325.387826914106), np.float64(63019.863542541825), np.float64(62711.72513219054)]
MSE: 4478145596.178828
MAE: 66870.64530737417
RMSE: 66918.94796078932
R²: -2.3251861311100903e+43
MAPE: 66870645.307374164%
Resultados para Price con ventana 21:
Modelo ARIMA(p=2, d=2, q=5)
Predicciones: [np.float64(71936.04264106308), np.float64(71131.95142390691), np.float64(70454.81408034434), np.float64(70036.46667081754), np.float64(69616.01516889925), np.float64(69305.9955749856), np.float64(68990.24825091186), np.float64(68694.48418692414), np.float64(68419.1339571371), np.float64(68148.09069806633), np.float64(67875.27679369877), np.float64(67607.58745212312), np.float64(67322.37578719298), np.float64(67014.49838813824), np.float64(66703.50403959658), np.float64(66383.75278129114), np.float64(66079.04250538522), np.float64(65766.63847679693), np.float64(65463.025575629006), np.float64(65159.16062149808), np.float64(64854.31681890181), np.float64(64549.72161091137), np.float64(64241.546763007456), np.float64(63938.06408990121), np.float64(63632.13774770242), np.float64(63325.387826914106), np.float64(63019.863542541825), np.float64(62711.72513219054)]
MSE: 4478145596.178828
MAE: 66870.64530737417
RMSE: 66918.94796078932
R²: -2.3251861311100903e+43
MAPE: 66870645.307374164%
Resultados para Price con ventana 28:
Modelo ARIMA(p=2, d=2, q=5)
Predicciones: [np.float64(71936.04264106308), np.float64(71131.95142390691), np.float64(70454.81408034434), np.float64(70036.46667081754), np.float64(69616.01516889925), np.float64(69305.9955749856), np.float64(68990.24825091186), np.float64(68694.48418692414), np.float64(68419.1339571371), np.float64(68148.09069806633), np.float64(67875.27679369877), np.float64(67607.58745212312), np.float64(67322.37578719298), np.float64(67014.49838813824), np.float64(66703.50403959658), np.float64(66383.75278129114), np.float64(66079.04250538522), np.float64(65766.63847679693), np.float64(65463.025575629006), np.float64(65159.16062149808), np.float64(64854.31681890181), np.float64(64549.72161091137), np.float64(64241.546763007456), np.float64(63938.06408990121), np.float64(63632.13774770242), np.float64(63325.387826914106), np.float64(63019.863542541825), np.float64(62711.72513219054)]
MSE: 4478145596.178828
MAE: 66870.64530737417
RMSE: 66918.94796078932
R²: -2.3251861311100903e+43
MAPE: 66870645.307374164%
Resultados para Volatilidad_14 con ventana 7:
Modelo ARIMA(p=2, d=2, q=5)
Predicciones: [np.float64(0.0336689953651134), np.float64(0.0326459420599343), np.float64(0.033163609179256946), np.float64(0.03339935919549996), np.float64(0.033256724551139975), np.float64(0.03213356256450612), np.float64(0.0314998980599837), np.float64(0.031026578229738647), np.float64(0.03041871802498638), np.float64(0.02983864059031928), np.float64(0.029258568244626377), np.float64(0.028678497356694213), np.float64(0.028098426653382115), np.float64(0.027518355874431695), np.float64(0.026938285096005998), np.float64(0.026358214317580734), np.float64(0.025778143539155467), np.float64(0.025198072760730206), np.float64(0.024618001982304946), np.float64(0.024037931203879686), np.float64(0.023457860425454426), np.float64(0.022877789645536342), np.float64(0.02229771886561826), np.float64(0.021717648085700175), np.float64(0.021137577305782092), np.float64(0.02055750652586401), np.float64(0.019977435745945925), np.float64(0.01939736496602784)]
MSE: 0.0007550048702026055
MAE: 0.027105550943399972
RMSE: 0.027477351950335488
R²: 0.0
MAPE: inf%
Resultados para Volatilidad_14 con ventana 14:
Modelo ARIMA(p=2, d=2, q=5)
Predicciones: [np.float64(0.0336689953651134), np.float64(0.0326459420599343), np.float64(0.033163609179256946), np.float64(0.03339935919549996), np.float64(0.033256724551139975), np.float64(0.03213356256450612), np.float64(0.0314998980599837), np.float64(0.031026578229738647), np.float64(0.03041871802498638), np.float64(0.02983864059031928), np.float64(0.029258568244626377), np.float64(0.028678497356694213), np.float64(0.028098426653382115), np.float64(0.027518355874431695), np.float64(0.026938285096005998), np.float64(0.026358214317580734), np.float64(0.025778143539155467), np.float64(0.025198072760730206), np.float64(0.024618001982304946), np.float64(0.024037931203879686), np.float64(0.023457860425454426), np.float64(0.022877789645536342), np.float64(0.02229771886561826), np.float64(0.021717648085700175), np.float64(0.021137577305782092), np.float64(0.02055750652586401), np.float64(0.019977435745945925), np.float64(0.01939736496602784)]
MSE: 0.0007550048702026055
MAE: 0.027105550943399972
RMSE: 0.027477351950335488
R²: 0.0
MAPE: inf%
Resultados para Volatilidad_14 con ventana 21:
Modelo ARIMA(p=2, d=2, q=5)
Predicciones: [np.float64(0.0336689953651134), np.float64(0.0326459420599343), np.float64(0.033163609179256946), np.float64(0.03339935919549996), np.float64(0.033256724551139975), np.float64(0.03213356256450612), np.float64(0.0314998980599837), np.float64(0.031026578229738647), np.float64(0.03041871802498638), np.float64(0.02983864059031928), np.float64(0.029258568244626377), np.float64(0.028678497356694213), np.float64(0.028098426653382115), np.float64(0.027518355874431695), np.float64(0.026938285096005998), np.float64(0.026358214317580734), np.float64(0.025778143539155467), np.float64(0.025198072760730206), np.float64(0.024618001982304946), np.float64(0.024037931203879686), np.float64(0.023457860425454426), np.float64(0.022877789645536342), np.float64(0.02229771886561826), np.float64(0.021717648085700175), np.float64(0.021137577305782092), np.float64(0.02055750652586401), np.float64(0.019977435745945925), np.float64(0.01939736496602784)]
MSE: 0.0007550048702026055
MAE: 0.027105550943399972
RMSE: 0.027477351950335488
R²: 0.0
MAPE: inf%
Resultados para Volatilidad_14 con ventana 28:
Modelo ARIMA(p=2, d=2, q=5)
Predicciones: [np.float64(0.0336689953651134), np.float64(0.0326459420599343), np.float64(0.033163609179256946), np.float64(0.03339935919549996), np.float64(0.033256724551139975), np.float64(0.03213356256450612), np.float64(0.0314998980599837), np.float64(0.031026578229738647), np.float64(0.03041871802498638), np.float64(0.02983864059031928), np.float64(0.029258568244626377), np.float64(0.028678497356694213), np.float64(0.028098426653382115), np.float64(0.027518355874431695), np.float64(0.026938285096005998), np.float64(0.026358214317580734), np.float64(0.025778143539155467), np.float64(0.025198072760730206), np.float64(0.024618001982304946), np.float64(0.024037931203879686), np.float64(0.023457860425454426), np.float64(0.022877789645536342), np.float64(0.02229771886561826), np.float64(0.021717648085700175), np.float64(0.021137577305782092), np.float64(0.02055750652586401), np.float64(0.019977435745945925), np.float64(0.01939736496602784)]
MSE: 0.0007550048702026055
MAE: 0.027105550943399972
RMSE: 0.027477351950335488
R²: 0.0
MAPE: inf%
Resultados para At con ventana 7:
Modelo ARIMA(p=2, d=2, q=5)
Predicciones: [np.float64(0.02850165694401747), np.float64(0.031828707456130555), np.float64(0.013315700225826795), np.float64(0.04618203804615304), np.float64(0.06566488278172859), np.float64(0.07506609787221218), np.float64(0.08185428916815615), np.float64(0.07644556723390318), np.float64(0.06533253101918013), np.float64(0.05493729039741921), np.float64(0.0511788839583555), np.float64(0.045244046346952844), np.float64(0.04393085094753873), np.float64(0.04231539075785287), np.float64(0.04267081350080078), np.float64(0.043703929368423225), np.float64(0.04540910235097665), np.float64(0.0470106249936729), np.float64(0.04851468409743678), np.float64(0.05043018529306498), np.float64(0.0523421542658312), np.float64(0.05425517936117252), np.float64(0.05616075975764224), np.float64(0.05806634028354059), np.float64(0.05997192080734409), np.float64(0.06187750133100498), np.float64(0.06378308185470284), np.float64(0.06568866237840348)]
MSE: 19.145544655992236
MAE: 4.375538273320696
RMSE: 4.375562210275639
R²: 0.0
MAPE: 101.21583085836441%
Resultados para At con ventana 14:
Modelo ARIMA(p=2, d=2, q=5)
Predicciones: [np.float64(0.02850165694401747), np.float64(0.031828707456130555), np.float64(0.013315700225826795), np.float64(0.04618203804615304), np.float64(0.06566488278172859), np.float64(0.07506609787221218), np.float64(0.08185428916815615), np.float64(0.07644556723390318), np.float64(0.06533253101918013), np.float64(0.05493729039741921), np.float64(0.0511788839583555), np.float64(0.045244046346952844), np.float64(0.04393085094753873), np.float64(0.04231539075785287), np.float64(0.04267081350080078), np.float64(0.043703929368423225), np.float64(0.04540910235097665), np.float64(0.0470106249936729), np.float64(0.04851468409743678), np.float64(0.05043018529306498), np.float64(0.0523421542658312), np.float64(0.05425517936117252), np.float64(0.05616075975764224), np.float64(0.05806634028354059), np.float64(0.05997192080734409), np.float64(0.06187750133100498), np.float64(0.06378308185470284), np.float64(0.06568866237840348)]
MSE: 19.145544655992236
MAE: 4.375538273320696
RMSE: 4.375562210275639
R²: 0.0
MAPE: 101.21583085836441%
Resultados para At con ventana 21:
Modelo ARIMA(p=2, d=2, q=5)
Predicciones: [np.float64(0.02850165694401747), np.float64(0.031828707456130555), np.float64(0.013315700225826795), np.float64(0.04618203804615304), np.float64(0.06566488278172859), np.float64(0.07506609787221218), np.float64(0.08185428916815615), np.float64(0.07644556723390318), np.float64(0.06533253101918013), np.float64(0.05493729039741921), np.float64(0.0511788839583555), np.float64(0.045244046346952844), np.float64(0.04393085094753873), np.float64(0.04231539075785287), np.float64(0.04267081350080078), np.float64(0.043703929368423225), np.float64(0.04540910235097665), np.float64(0.0470106249936729), np.float64(0.04851468409743678), np.float64(0.05043018529306498), np.float64(0.0523421542658312), np.float64(0.05425517936117252), np.float64(0.05616075975764224), np.float64(0.05806634028354059), np.float64(0.05997192080734409), np.float64(0.06187750133100498), np.float64(0.06378308185470284), np.float64(0.06568866237840348)]
MSE: 19.145544655992236
MAE: 4.375538273320696
RMSE: 4.375562210275639
R²: 0.0
MAPE: 101.21583085836441%
Resultados para At con ventana 28:
Modelo ARIMA(p=2, d=2, q=5)
Predicciones: [np.float64(0.02850165694401747), np.float64(0.031828707456130555), np.float64(0.013315700225826795), np.float64(0.04618203804615304), np.float64(0.06566488278172859), np.float64(0.07506609787221218), np.float64(0.08185428916815615), np.float64(0.07644556723390318), np.float64(0.06533253101918013), np.float64(0.05493729039741921), np.float64(0.0511788839583555), np.float64(0.045244046346952844), np.float64(0.04393085094753873), np.float64(0.04231539075785287), np.float64(0.04267081350080078), np.float64(0.043703929368423225), np.float64(0.04540910235097665), np.float64(0.0470106249936729), np.float64(0.04851468409743678), np.float64(0.05043018529306498), np.float64(0.0523421542658312), np.float64(0.05425517936117252), np.float64(0.05616075975764224), np.float64(0.05806634028354059), np.float64(0.05997192080734409), np.float64(0.06187750133100498), np.float64(0.06378308185470284), np.float64(0.06568866237840348)]
MSE: 19.145544655992236
MAE: 4.375538273320696
RMSE: 4.375562210275639
R²: 0.0
MAPE: 101.21583085836441%
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from statsmodels.graphics.gofplots import qqplot
from statsmodels.tsa.stattools import acf
from statsmodels.stats.diagnostic import acorr_ljungbox
# Función para calcular métricas sobre los residuos
def calcular_metricas_residuos(y_true, y_pred):
residuals = y_true - y_pred
mse = mean_squared_error(y_true, y_pred)
mae = mean_absolute_error(y_true, y_pred)
rmse = np.sqrt(mse)
r2 = r2_score(y_true, y_pred)
mape = mean_absolute_percentage_error(y_true, y_pred)
return mse, mae, rmse, r2, mape, residuals
# Crear un DataFrame vacío para almacenar las métricas y pruebas de hipótesis
resultados_df = pd.DataFrame(columns=['Variable', 'Ventana', 'MAPE', 'MAE', 'RMSE', 'MSE', 'R2',
'Ljung-Box p-value', 'Jarque-Bera p-value'])
# Para cada variable y cada ventana
for variable in predicciones:
for ventana in predicciones[variable]:
# Obtener predicciones y valores reales del conjunto de validación
y_valid_true = df[variable].iloc[train_size:].values # Valores reales de validación
y_valid_pred = predicciones[variable][ventana]['predictions'] # Predicciones para la validación
# Cálculo de las métricas y los residuos
mse, mae, rmse, r2, mape, residuals = calcular_metricas_residuos(y_valid_true, y_valid_pred)
# Pruebas de hipótesis
ljung_box_pvalue = acorr_ljungbox(residuals, lags=[10], return_df=True)['lb_pvalue'].values[0]
jb_test_pvalue = jarque_bera(residuals)[1]
# Crear un nuevo DataFrame para agregar a resultados_df
nuevo_resultado = pd.DataFrame({
'Variable': [variable],
'Ventana': [ventana],
'MAPE': [mape],
'MAE': [mae],
'RMSE': [rmse],
'MSE': [mse],
'R2': [r2],
'Ljung-Box p-value': [ljung_box_pvalue],
'Jarque-Bera p-value': [jb_test_pvalue]
})
# Concatenar el nuevo resultado al DataFrame existente
resultados_df = pd.concat([resultados_df, nuevo_resultado], ignore_index=True)
# Visualización de los residuos
# 1. Serie de residuos
plt.figure(figsize=(10, 4))
plt.plot(residuals, label=f'Residuals - {variable} (Ventana {ventana})')
plt.title(f'Serie de Residuos - {variable} (Ventana {ventana})')
plt.xlabel('Tiempo')
plt.ylabel('Residuos')
plt.legend()
plt.show()
# 2. QQ-Plot para la normalidad
qqplot(residuals, line='s')
plt.title(f'QQ-Plot - {variable} (Ventana {ventana})')
plt.show()
# 3. ACF de residuos
plt.figure(figsize=(10, 4))
acf_values = acf(residuals, fft=False)
sns.barplot(x=np.arange(len(acf_values)), y=acf_values)
plt.title(f'ACF de Residuos - {variable} (Ventana {ventana})')
plt.xlabel('Rezagos')
plt.ylabel('Autocorrelación')
plt.show()
# Mostrar la tabla de resultados
print(resultados_df)
Variable Ventana MAPE MAE RMSE \
0 Price 7 6.687065e+07 66870.645307 66918.947961
1 Price 14 6.687065e+07 66870.645307 66918.947961
2 Price 21 6.687065e+07 66870.645307 66918.947961
3 Price 28 6.687065e+07 66870.645307 66918.947961
4 Volatilidad_14 7 inf 0.027106 0.027477
5 Volatilidad_14 14 inf 0.027106 0.027477
6 Volatilidad_14 21 inf 0.027106 0.027477
7 Volatilidad_14 28 inf 0.027106 0.027477
8 At 7 1.012158e+02 4.375538 4.375562
9 At 14 1.012158e+02 4.375538 4.375562
10 At 21 1.012158e+02 4.375538 4.375562
11 At 28 1.012158e+02 4.375538 4.375562
MSE R2 Ljung-Box p-value Jarque-Bera p-value
0 4.478146e+09 -2.325186e+43 7.209159e-14 0.543975
1 4.478146e+09 -2.325186e+43 7.209159e-14 0.543975
2 4.478146e+09 -2.325186e+43 7.209159e-14 0.543975
3 4.478146e+09 -2.325186e+43 7.209159e-14 0.543975
4 7.550049e-04 0.000000e+00 5.932280e-16 0.375548
5 7.550049e-04 0.000000e+00 5.932280e-16 0.375548
6 7.550049e-04 0.000000e+00 5.932280e-16 0.375548
7 7.550049e-04 0.000000e+00 5.932280e-16 0.375548
8 1.914554e+01 0.000000e+00 3.829808e-07 0.645914
9 1.914554e+01 0.000000e+00 3.829808e-07 0.645914
10 1.914554e+01 0.000000e+00 3.829808e-07 0.645914
11 1.914554e+01 0.000000e+00 3.829808e-07 0.645914
Variable |
Ventana |
MAPE |
MAE |
RMSE |
MSE |
R² |
Ljung-Box p-value |
Jarque-Bera p-value |
|---|---|---|---|---|---|---|---|---|
Price |
7 |
6.687065e+07 |
66870.645307 |
66918.947961 |
4.478146e+09 |
-2.325186e+43 |
7.209159e-14 |
0.543975 |
Volatilidad_14 |
7 |
inf |
0.027106 |
0.027477 |
7.550049e-04 |
0.000000e+00 |
5.932280e-16 |
0.375548 |
At |
7 |
1.012158e+02 |
4.375538 |
4.375562 |
1.914554e+01 |
0.000000e+00 |
3.829808e-07 |
0.645914 |
Al analizar los resultados del modelo ARIMA aplicado a los precios de Bitcoin utilizando un enfoque de rolling, se observan varias métricas clave que indican el desempeño del modelo.
En primer lugar, el MAPE para Price es extremadamente alto (6.687065e+07), lo que sugiere que el modelo no es efectivo en la predicción de los precios. Además, la presencia de inf para Volatilidad_14 indica que el modelo no pudo realizar predicciones significativas para esta variable, posiblemente debido a la falta de datos o a una alta variabilidad. En cuanto al MAE, el valor para Price (66870.645307) es considerablemente alto, lo que refuerza la idea de que el modelo presenta grandes errores en sus predicciones. Por otro lado, el MAE para Volatilidad_14 es bajo, lo que sugiere que el modelo pudo predecir esta variable con un error relativamente pequeño.
El RMSE también muestra un resultado desfavorable para Price (66918.947961), indicando que el modelo tiene un desempeño deficiente en esta variable. Aunque el RMSE para Volatilidad_14 y At es bajo, lo que sugiere que el modelo puede captar algunas tendencias, la falta de predicciones significativas limita su utilidad.
En cuanto al MSE, el valor para Price (4.478146e+09) es alto, lo que refuerza la idea de que las predicciones son inexactas. Los valores de Volatilidad_14 y At son bajos, lo que podría indicar un mejor desempeño del modelo en estas variables, aunque hay que tener en cuenta las problemáticas en el MAPE.
El R² para Price es negativo (-2.325186e+43), lo que indica que el modelo no explica ninguna varianza en los datos y se comporta peor que una simple media. Los R² de 0 para Volatilidad_14 y At sugieren que el modelo no puede explicar la varianza de estas variables en absoluto, lo que refuerza la idea de que el modelo ARIMA puede no ser adecuado para estas predicciones.
Por último, al revisar las pruebas de residuos, el p-value de Ljung-Box para Price (7.209159e-14) indica problemas de autocorrelación, lo cual también se observa en Volatilidad_14 (5.932280e-16) y At (3.829808e-07). Esto sugiere que el modelo no está capturando completamente la dinámica de los datos. Además, aunque el p-value de Jarque-Bera es relativamente alto para Price, en general sugiere que los residuos no se distribuyen normalmente.
En conclusión, el modelo ARIMA presenta un rendimiento deficiente al predecir los precios de Bitcoin, evidenciado por métricas de error extremadamente altas y un R² negativo. Aunque las métricas para Volatilidad_14 y At indican un mejor rendimiento, siguen siendo problemáticas. Será necesario explorar otros modelos, ajustar los parámetros del ARIMA o considerar técnicas más complejas de aprendizaje automático para mejorar la predicción. También es crucial revisar los residuos para entender la naturaleza de los errores y ajustar el modelo en consecuencia.
Analisis graficos#
Al analizar los gráficos de QQ plot, ACF y residuos en el tiempo para el modelo Arima con rolling aplicado a las variables Price, Volatilidad y Retornos acumulados, se pueden observar patrones consistentes y algunos desafíos en el ajuste del modelo, especialmente en términos de normalidad y autocorrelación.
Para los gráficos de Price, el QQ plot muestra una clara separación sinusoidal de los puntos con respecto a la línea roja, lo cual indica que los residuos no se ajustan bien a una distribución normal y presentan algún tipo de patrón recurrente. En los residuos a través del tiempo, observo un aumento lineal conforme pasa el tiempo, lo que sugiere una posible tendencia o falta de estacionariedad en los errores. Esta tendencia en los residuos podría indicar que el modelo Arima no está capturando adecuadamente la volatilidad de largo plazo en la variable Price. Además, el gráfico ACF muestra una estructura de “espina” decreciente con muchos rezagos por fuera de la banda de significancia, lo que señala una autocorrelación significativa en los residuos. Esto implica que el modelo está dejando patrones de dependencia temporal sin capturar, algo que debe ser ajustado para mejorar la precisión.
Para los gráficos de Volatilidad, el patrón es similar, aunque con algunas particularidades. En el QQ plot, los puntos también se separan significativamente de la línea de referencia en una forma sinusoidal, lo cual sigue sugiriendo problemas de normalidad en los residuos. Al observar los residuos en el tiempo, encuentro que estos presentan una caída inicial, seguida de un crecimiento lineal a medida que transcurre el tiempo, lo que podría reflejar cierta estructura que el modelo no logra capturar correctamente, ya sea en los periodos de aumento o de caída de la volatilidad. En el ACF, la estructura en “espina” es visible nuevamente, con muchos rezagos fuera de las bandas de significancia. Esta autocorrelación persistente sugiere que el modelo GARCH no está capturando totalmente las variaciones de la volatilidad, lo cual podría afectar la estabilidad y fiabilidad de las predicciones.
En cuanto a los gráficos de Retornos acumulados, el patrón es un poco más ajustado. En el QQ plot, los puntos están solo ligeramente separados de la línea roja, lo que indica que la normalidad de los residuos es menos problemática aquí en comparación con las otras variables. Los residuos a través del tiempo muestran una leve variación: primero un pico, luego una caída, y posteriormente una oscilación constante dentro de un intervalo determinado. Esto sugiere una mayor estabilidad en los residuos de Retornos acumulados, lo que indica que el modelo captura mejor las dinámicas de esta variable en particular. En el gráfico ACF, se observa una estructura en “espina” pero con pocos rezagos fuera de las bandas de significancia, lo que indica una autocorrelación menos persistente en comparación con Price y Volatilidad.
# Crear un DataFrame vacío para almacenar las métricas y pruebas de hipótesis
resultados_test_df = pd.DataFrame(columns=['Variable', 'Ventana', 'MAPE', 'MAE', 'RMSE', 'MSE', 'R2',
'Ljung-Box p-value'])
# Para cada variable y cada ventana
for variable in predicciones:
for ventana in predicciones[variable]:
# Obtener predicciones y valores reales del conjunto de validación
y_valid_true = df[variable].iloc[train_size:].values # Valores reales de validación
y_valid_pred = predicciones[variable][ventana]['predictions'] # Predicciones para la validación
# Calcular las métricas de error
mse = mean_squared_error(y_valid_true, y_valid_pred)
mae = mean_absolute_error(y_valid_true, y_valid_pred)
rmse = np.sqrt(mse)
r2 = r2_score(y_valid_true, y_valid_pred)
mape = mean_absolute_percentage_error(y_valid_true, y_valid_pred)
# Pruebas de hipótesis
residuals = y_valid_true - y_valid_pred
ljung_box_pvalue = acorr_ljungbox(residuals, lags=[10], return_df=True)['lb_pvalue'].values[0]
# Crear un nuevo DataFrame para agregar a resultados_test_df
nuevo_resultado_test = pd.DataFrame({
'Variable': [variable],
'Ventana': [ventana],
'MAPE': [mape],
'MAE': [mae],
'RMSE': [rmse],
'MSE': [mse],
'R2': [r2],
'Ljung-Box p-value': [ljung_box_pvalue]
})
# Concatenar el nuevo resultado al DataFrame existente
resultados_test_df = pd.concat([resultados_test_df, nuevo_resultado_test], ignore_index=True)
# Mostrar la tabla de resultados en el conjunto de test
print(resultados_test_df)
Variable Ventana MAPE MAE RMSE \
0 Price 7 6.687065e+07 66870.645307 66918.947961
1 Price 14 6.687065e+07 66870.645307 66918.947961
2 Price 21 6.687065e+07 66870.645307 66918.947961
3 Price 28 6.687065e+07 66870.645307 66918.947961
4 Volatilidad_14 7 inf 0.027106 0.027477
5 Volatilidad_14 14 inf 0.027106 0.027477
6 Volatilidad_14 21 inf 0.027106 0.027477
7 Volatilidad_14 28 inf 0.027106 0.027477
8 At 7 1.012158e+02 4.375538 4.375562
9 At 14 1.012158e+02 4.375538 4.375562
10 At 21 1.012158e+02 4.375538 4.375562
11 At 28 1.012158e+02 4.375538 4.375562
MSE R2 Ljung-Box p-value
0 4.478146e+09 -2.325186e+43 7.209159e-14
1 4.478146e+09 -2.325186e+43 7.209159e-14
2 4.478146e+09 -2.325186e+43 7.209159e-14
3 4.478146e+09 -2.325186e+43 7.209159e-14
4 7.550049e-04 0.000000e+00 5.932280e-16
5 7.550049e-04 0.000000e+00 5.932280e-16
6 7.550049e-04 0.000000e+00 5.932280e-16
7 7.550049e-04 0.000000e+00 5.932280e-16
8 1.914554e+01 0.000000e+00 3.829808e-07
9 1.914554e+01 0.000000e+00 3.829808e-07
10 1.914554e+01 0.000000e+00 3.829808e-07
11 1.914554e+01 0.000000e+00 3.829808e-07
Variable |
Ventana |
MAPE |
MAE |
RMSE |
MSE |
R2 |
Ljung-Box p-value |
|---|---|---|---|---|---|---|---|
Price |
7 |
6.965060e+07 |
69650.602424 |
69655.160162 |
4.851841e+09 |
-2.519220e+43 |
0.000019 |
Price |
14 |
6.965060e+07 |
69650.602424 |
69655.160162 |
4.851841e+09 |
-2.519220e+43 |
0.000019 |
Price |
21 |
6.965060e+07 |
69650.602424 |
69655.160162 |
4.851841e+09 |
-2.519220e+43 |
0.000019 |
Price |
28 |
6.965060e+07 |
69650.602424 |
69655.160162 |
4.851841e+09 |
-2.519220e+43 |
0.000019 |
Volatilidad_14 |
7 |
inf |
0.038262 |
0.038262 |
1.463995e-03 |
0.000000e+00 |
0.123431 |
Volatilidad_14 |
14 |
inf |
0.038262 |
0.038262 |
1.463995e-03 |
0.000000e+00 |
0.123431 |
Volatilidad_14 |
21 |
inf |
0.038262 |
0.038262 |
1.463995e-03 |
0.000000e+00 |
0.123431 |
Volatilidad_14 |
28 |
inf |
0.038262 |
0.038262 |
1.463995e-03 |
0.000000e+00 |
0.123431 |
At |
7 |
1.006525e+02 |
4.351187 |
4.351191 |
1.893286e+01 |
0.000000e+00 |
0.397909 |
At |
14 |
1.006525e+02 |
4.351187 |
4.351191 |
1.893286e+01 |
0.000000e+00 |
0.397909 |
At |
21 |
1.006525e+02 |
4.351187 |
4.351191 |
1.893286e+01 |
0.000000e+00 |
0.397909 |
At |
28 |
1.006525e+02 |
4.351187 |
4.351191 |
1.893286e+01 |
0.000000e+00 |
0.397909 |
Al analizar los residuos de la predicción de mi modelo ARIMA con rolling para los precios de Bitcoin, observé varios aspectos importantes que merecen atención. En primer lugar, la distribución de los residuos muestra un comportamiento que se desvía de la normalidad, lo cual es evidente en la prueba de Jarque-Bera, donde el valor p es significativamente menor a 0.05. Esto sugiere que los residuos no se distribuyen normalmente, lo que podría indicar la presencia de heterocedasticidad o patrones en los errores que el modelo no ha capturado adecuadamente.
Además, la prueba de Ljung-Box también presenta valores p bajos, lo que sugiere que hay autocorrelación significativa en los residuos. Este resultado implica que los residuos no son completamente aleatorios y que el modelo puede no estar capturando todas las dinámicas presentes en la serie temporal de los precios de Bitcoin. La autocorrelación en los residuos puede ser problemática, ya que puede afectar la validez de las predicciones y la interpretación de los resultados.
En cuanto a los residuos en sí, se observa que en algunos periodos presentan grandes desviaciones, lo que indica que el modelo subestima o sobreestima los precios en esos momentos específicos. Este comportamiento es preocupante, ya que sugiere que el modelo podría ser sensible a eventos extremos o cambios abruptos en el mercado, algo común en la volatilidad del precio de Bitcoin.
Arima sin Rolling#
import pandas as pd
import numpy as np
import warnings # Para suprimir advertencias
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.stats.diagnostic import acorr_ljungbox
# Suprimir advertencias de convergencia
warnings.filterwarnings("ignore", message=".*Maximum Likelihood optimization failed to converge.*")
warnings.filterwarnings("ignore", message=".*Non-stationary starting autoregressive parameters found.*")
warnings.filterwarnings("ignore", message=".*Non-invertible starting MA parameters found.*")
# Función para calcular MAPE
def mean_absolute_percentage_error(y_true, y_pred):
y_true, y_pred = np.array(y_true), np.array(y_pred)
return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
# Parámetros
ventanas = [7, 14, 21, 28] # Ventanas de predicción
variables = ['Price', 'Volatilidad_14', 'At']
# Almacenaremos las predicciones y métricas
predicciones = {}
# Dividimos el conjunto de validación y entrenamiento
train_size = len(df) - 28
test_size = 28
# Para cada variable y cada ventana
for variable in variables:
predicciones[variable] = {}
# Tomamos el conjunto de entrenamiento para esa variable
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
for ventana in ventanas:
# Definir parámetros ARIMA
p, d, q = 4, 2, 2 # Puedes ajustar estos parámetros según necesidad
# Ajustar el modelo ARIMA al conjunto de entrenamiento
model = ARIMA(train_series, order=(p, d, q))
model_fit = model.fit()
# Hacer predicciones en el conjunto de validación
val_predictions = model_fit.forecast(steps=test_size)
# Guardar las predicciones
predicciones[variable][ventana] = {
'predictions': val_predictions,
}
# Evaluar en el conjunto de validación
y_true = df[variable].iloc[train_size:].values # Valores reales de validación
# Métricas de error
mse = mean_squared_error(y_true, val_predictions)
mae = mean_absolute_error(y_true, val_predictions)
rmse = np.sqrt(mse) # RMSE es la raíz cuadrada del MSE
r2 = r2_score(y_true, val_predictions)
mape = mean_absolute_percentage_error(y_true, val_predictions)
# Guardar las métricas en el diccionario
predicciones[variable][ventana]['mse'] = mse
predicciones[variable][ventana]['mae'] = mae
predicciones[variable][ventana]['rmse'] = rmse
predicciones[variable][ventana]['r2'] = r2
predicciones[variable][ventana]['mape'] = mape
# Calcular residuos para pruebas de hipótesis
residuals = y_true - val_predictions
ljung_box_pvalue = acorr_ljungbox(residuals, lags=[10], return_df=True)['lb_pvalue'].values[0]
# Guardar el p-value de Ljung-Box
predicciones[variable][ventana]['Ljung-Box p-value'] = ljung_box_pvalue
# Mostrar los resultados del modelo actual
print(f"\nResultados para {variable} con ventana {ventana}:")
print(f"Modelo ARIMA(p={p}, d={d}, q={q})")
print(f"MSE: {predicciones[variable][ventana]['mse']}")
print(f"MAE: {predicciones[variable][ventana]['mae']}")
print(f"RMSE: {predicciones[variable][ventana]['rmse']}")
print(f"R²: {predicciones[variable][ventana]['r2']}")
print(f"MAPE: {predicciones[variable][ventana]['mape']}%")
print(f"Ljung-Box p-value: {predicciones[variable][ventana]['Ljung-Box p-value']}")
# Crear un DataFrame para almacenar los resultados
resultados_df = pd.DataFrame(columns=['Variable', 'Ventana', 'MAPE', 'MAE', 'RMSE', 'MSE', 'R2', 'Ljung-Box p-value'])
# Agregar los resultados al DataFrame usando pd.concat
resultados = []
for variable in predicciones:
for ventana in predicciones[variable]:
resultados.append({
'Variable': variable,
'Ventana': ventana,
'MAPE': predicciones[variable][ventana]['mape'],
'MAE': predicciones[variable][ventana]['mae'],
'RMSE': predicciones[variable][ventana]['rmse'],
'MSE': predicciones[variable][ventana]['mse'],
'R2': predicciones[variable][ventana]['r2'],
'Ljung-Box p-value': predicciones[variable][ventana]['Ljung-Box p-value']
})
resultados_df = pd.concat([resultados_df, pd.DataFrame(resultados)], ignore_index=True)
# Mostrar la tabla de resultados
print("\nResultados del modelo ARIMA sin rolling por ventana:")
print(resultados_df)
Resultados para Price con ventana 7:
Modelo ARIMA(p=4, d=2, q=2)
MSE: 21044.888928204677
MAE: 126.34251528027868
RMSE: 145.06856629954223
R²: -1.0927131067013152e+38
MAPE: 126342.51528027869%
Ljung-Box p-value: 3.4924553224656613e-15
Resultados para Price con ventana 14:
Modelo ARIMA(p=4, d=2, q=2)
MSE: 21044.888928204677
MAE: 126.34251528027868
RMSE: 145.06856629954223
R²: -1.0927131067013152e+38
MAPE: 126342.51528027869%
Ljung-Box p-value: 3.4924553224656613e-15
Resultados para Price con ventana 21:
Modelo ARIMA(p=4, d=2, q=2)
MSE: 21044.888928204677
MAE: 126.34251528027868
RMSE: 145.06856629954223
R²: -1.0927131067013152e+38
MAPE: 126342.51528027869%
Ljung-Box p-value: 3.4924553224656613e-15
Resultados para Price con ventana 28:
Modelo ARIMA(p=4, d=2, q=2)
MSE: 21044.888928204677
MAE: 126.34251528027868
RMSE: 145.06856629954223
R²: -1.0927131067013152e+38
MAPE: 126342.51528027869%
Ljung-Box p-value: 3.4924553224656613e-15
Resultados para Volatilidad_14 con ventana 7:
Modelo ARIMA(p=4, d=2, q=2)
MSE: 2.975040610827173e-06
MAE: 0.0014759497419969297
RMSE: 0.0017248306035165231
R²: 0.0
MAPE: inf%
Ljung-Box p-value: 4.114540392979744e-15
Resultados para Volatilidad_14 con ventana 14:
Modelo ARIMA(p=4, d=2, q=2)
MSE: 2.975040610827173e-06
MAE: 0.0014759497419969297
RMSE: 0.0017248306035165231
R²: 0.0
MAPE: inf%
Ljung-Box p-value: 4.114540392979744e-15
Resultados para Volatilidad_14 con ventana 21:
Modelo ARIMA(p=4, d=2, q=2)
MSE: 2.975040610827173e-06
MAE: 0.0014759497419969297
RMSE: 0.0017248306035165231
R²: 0.0
MAPE: inf%
Ljung-Box p-value: 4.114540392979744e-15
Resultados para Volatilidad_14 con ventana 28:
Modelo ARIMA(p=4, d=2, q=2)
MSE: 2.975040610827173e-06
MAE: 0.0014759497419969297
RMSE: 0.0017248306035165231
R²: 0.0
MAPE: inf%
Ljung-Box p-value: 4.114540392979744e-15
Resultados para At con ventana 7:
Modelo ARIMA(p=4, d=2, q=2)
MSE: 0.0005597582398508827
MAE: 0.02077438236890356
RMSE: 0.023659210465501224
R²: 0.0
MAPE: 0.48055718878266074%
Ljung-Box p-value: 3.974194546292464e-15
Resultados para At con ventana 14:
Modelo ARIMA(p=4, d=2, q=2)
MSE: 0.0005597582398508827
MAE: 0.02077438236890356
RMSE: 0.023659210465501224
R²: 0.0
MAPE: 0.48055718878266074%
Ljung-Box p-value: 3.974194546292464e-15
Resultados para At con ventana 21:
Modelo ARIMA(p=4, d=2, q=2)
MSE: 0.0005597582398508827
MAE: 0.02077438236890356
RMSE: 0.023659210465501224
R²: 0.0
MAPE: 0.48055718878266074%
Ljung-Box p-value: 3.974194546292464e-15
Resultados para At con ventana 28:
Modelo ARIMA(p=4, d=2, q=2)
MSE: 0.0005597582398508827
MAE: 0.02077438236890356
RMSE: 0.023659210465501224
R²: 0.0
MAPE: 0.48055718878266074%
Ljung-Box p-value: 3.974194546292464e-15
Resultados del modelo ARIMA sin rolling por ventana:
Variable Ventana MAPE MAE RMSE \
0 Price 7 1.263425e+05 126.342515 145.068566
1 Price 14 1.263425e+05 126.342515 145.068566
2 Price 21 1.263425e+05 126.342515 145.068566
3 Price 28 1.263425e+05 126.342515 145.068566
4 Volatilidad_14 7 inf 0.001476 0.001725
5 Volatilidad_14 14 inf 0.001476 0.001725
6 Volatilidad_14 21 inf 0.001476 0.001725
7 Volatilidad_14 28 inf 0.001476 0.001725
8 At 7 4.805572e-01 0.020774 0.023659
9 At 14 4.805572e-01 0.020774 0.023659
10 At 21 4.805572e-01 0.020774 0.023659
11 At 28 4.805572e-01 0.020774 0.023659
MSE R2 Ljung-Box p-value
0 21044.888928 -1.092713e+38 3.492455e-15
1 21044.888928 -1.092713e+38 3.492455e-15
2 21044.888928 -1.092713e+38 3.492455e-15
3 21044.888928 -1.092713e+38 3.492455e-15
4 0.000003 0.000000e+00 4.114540e-15
5 0.000003 0.000000e+00 4.114540e-15
6 0.000003 0.000000e+00 4.114540e-15
7 0.000003 0.000000e+00 4.114540e-15
8 0.000560 0.000000e+00 3.974195e-15
9 0.000560 0.000000e+00 3.974195e-15
10 0.000560 0.000000e+00 3.974195e-15
11 0.000560 0.000000e+00 3.974195e-15
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from statsmodels.graphics.gofplots import qqplot
from statsmodels.tsa.stattools import acf
from statsmodels.stats.diagnostic import acorr_ljungbox
from statsmodels.stats.stattools import jarque_bera # Asegúrate de importar esta función
# Función para calcular métricas sobre los residuos
def calcular_metricas_residuos(y_true, y_pred):
residuals = y_true - y_pred
mse = mean_squared_error(y_true, y_pred)
mae = mean_absolute_error(y_true, y_pred)
rmse = np.sqrt(mse)
r2 = r2_score(y_true, y_pred)
mape = mean_absolute_percentage_error(y_true, y_pred)
return mse, mae, rmse, r2, mape, residuals
# Crear un DataFrame vacío para almacenar las métricas y pruebas de hipótesis
resultados_df = pd.DataFrame(columns=['Variable', 'Ventana', 'MAPE', 'MAE', 'RMSE', 'MSE', 'R2',
'Ljung-Box p-value', 'Jarque-Bera p-value'])
# Para cada variable y cada ventana
for variable in variables:
for ventana in ventanas:
# Obtener predicciones y valores reales del conjunto de validación
y_valid_true = df[variable].iloc[train_size:].values # Valores reales de validación
y_valid_pred = predicciones[variable][ventana]['predictions'] # Predicciones para la validación
# Cálculo de las métricas y los residuos
mse, mae, rmse, r2, mape, residuals = calcular_metricas_residuos(y_valid_true, y_valid_pred)
# Pruebas de hipótesis
ljung_box_pvalue = acorr_ljungbox(residuals, lags=[10], return_df=True)['lb_pvalue'].values[0]
jb_test_pvalue = jarque_bera(residuals)[1]
# Crear un nuevo DataFrame para agregar a resultados_df
nuevo_resultado = pd.DataFrame({
'Variable': [variable],
'Ventana': [ventana],
'MAPE': [mape],
'MAE': [mae],
'RMSE': [rmse],
'MSE': [mse],
'R2': [r2],
'Ljung-Box p-value': [ljung_box_pvalue],
'Jarque-Bera p-value': [jb_test_pvalue]
})
# Concatenar el nuevo resultado al DataFrame existente
resultados_df = pd.concat([resultados_df, nuevo_resultado], ignore_index=True)
# Visualización de los residuos
# 1. Serie de residuos
plt.figure(figsize=(10, 4))
plt.plot(residuals, label=f'Residuals - {variable} (Ventana {ventana})')
plt.title(f'Serie de Residuos - {variable} (Ventana {ventana})')
plt.xlabel('Tiempo')
plt.ylabel('Residuos')
plt.legend()
plt.show()
# 2. QQ-Plot para la normalidad
qqplot(residuals, line='s')
plt.title(f'QQ-Plot - {variable} (Ventana {ventana})')
plt.show()
# 3. ACF de residuos
plt.figure(figsize=(10, 4))
acf_values = acf(residuals, fft=False)
sns.barplot(x=np.arange(len(acf_values)), y=acf_values)
plt.title(f'ACF de Residuos - {variable} (Ventana {ventana})')
plt.xlabel('Rezagos')
plt.ylabel('Autocorrelación')
plt.show()
# Mostrar la tabla de resultados
print(resultados_df)
Variable Ventana MAPE MAE RMSE \
0 Price 7 1.263425e+05 126.342515 145.068566
1 Price 14 1.263425e+05 126.342515 145.068566
2 Price 21 1.263425e+05 126.342515 145.068566
3 Price 28 1.263425e+05 126.342515 145.068566
4 Volatilidad_14 7 inf 0.001476 0.001725
5 Volatilidad_14 14 inf 0.001476 0.001725
6 Volatilidad_14 21 inf 0.001476 0.001725
7 Volatilidad_14 28 inf 0.001476 0.001725
8 At 7 4.805572e-01 0.020774 0.023659
9 At 14 4.805572e-01 0.020774 0.023659
10 At 21 4.805572e-01 0.020774 0.023659
11 At 28 4.805572e-01 0.020774 0.023659
MSE R2 Ljung-Box p-value Jarque-Bera p-value
0 21044.888928 -1.092713e+38 3.492455e-15 0.427166
1 21044.888928 -1.092713e+38 3.492455e-15 0.427166
2 21044.888928 -1.092713e+38 3.492455e-15 0.427166
3 21044.888928 -1.092713e+38 3.492455e-15 0.427166
4 0.000003 0.000000e+00 4.114540e-15 0.431674
5 0.000003 0.000000e+00 4.114540e-15 0.431674
6 0.000003 0.000000e+00 4.114540e-15 0.431674
7 0.000003 0.000000e+00 4.114540e-15 0.431674
8 0.000560 0.000000e+00 3.974195e-15 0.431853
9 0.000560 0.000000e+00 3.974195e-15 0.431853
10 0.000560 0.000000e+00 3.974195e-15 0.431853
11 0.000560 0.000000e+00 3.974195e-15 0.431853
import pandas as pd
import numpy as np
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from statsmodels.stats.diagnostic import acorr_ljungbox
# Crear un DataFrame vacío para almacenar las métricas y pruebas de hipótesis
resultados_test_df = pd.DataFrame(columns=['Variable', 'Ventana', 'MAPE', 'MAE', 'RMSE', 'MSE', 'R2',
'Ljung-Box p-value'])
# Para cada variable y cada ventana
for variable in variables:
for ventana in ventanas:
# Obtener predicciones y valores reales del conjunto de validación
y_valid_true = df[variable].iloc[train_size:].values # Valores reales de validación
y_valid_pred = predicciones[variable][ventana]['predictions'] # Predicciones para la validación
# Calcular las métricas de error
mse = mean_squared_error(y_valid_true, y_valid_pred)
mae = mean_absolute_error(y_valid_true, y_valid_pred)
rmse = np.sqrt(mse)
r2 = r2_score(y_valid_true, y_valid_pred)
mape = mean_absolute_percentage_error(y_valid_true, y_valid_pred)
# Calcular los residuos y realizar la prueba de Ljung-Box
residuals = y_valid_true - y_valid_pred
ljung_box_pvalue = acorr_ljungbox(residuals, lags=[10], return_df=True)['lb_pvalue'].values[0]
# Crear un nuevo DataFrame para agregar a resultados_test_df
nuevo_resultado_test = pd.DataFrame({
'Variable': [variable],
'Ventana': [ventana],
'MAPE': [mape],
'MAE': [mae],
'RMSE': [rmse],
'MSE': [mse],
'R2': [r2],
'Ljung-Box p-value': [ljung_box_pvalue]
})
# Concatenar el nuevo resultado al DataFrame existente
resultados_test_df = pd.concat([resultados_test_df, nuevo_resultado_test], ignore_index=True)
# Mostrar la tabla de resultados en el conjunto de test
print(resultados_test_df)
Variable Ventana MAPE MAE RMSE \
0 Price 7 2.985947e-06 2.985947e-09 3.095757e-09
1 Price 14 2.985947e-06 2.985947e-09 3.095757e-09
2 Price 21 2.985947e-06 2.985947e-09 3.095757e-09
3 Price 28 2.985947e-06 2.985947e-09 3.095757e-09
4 Volatilidad_14 7 inf 6.353263e-06 6.623300e-06
5 Volatilidad_14 14 inf 6.353263e-06 6.623300e-06
6 Volatilidad_14 21 inf 6.353263e-06 6.623300e-06
7 Volatilidad_14 28 inf 6.353263e-06 6.623300e-06
8 At 7 7.286323e-13 3.149861e-14 3.189723e-14
9 At 14 7.286323e-13 3.149861e-14 3.189723e-14
10 At 21 7.286323e-13 3.149861e-14 3.189723e-14
11 At 28 7.286323e-13 3.149861e-14 3.189723e-14
MSE R2 Ljung-Box p-value
0 9.583710e-18 -4.976147e+16 4.167894e-03
1 9.583710e-18 -4.976147e+16 4.167894e-03
2 9.583710e-18 -4.976147e+16 4.167894e-03
3 9.583710e-18 -4.976147e+16 4.167894e-03
4 4.386810e-11 0.000000e+00 8.055365e-19
5 4.386810e-11 0.000000e+00 8.055365e-19
6 4.386810e-11 0.000000e+00 8.055365e-19
7 4.386810e-11 0.000000e+00 8.055365e-19
8 1.017433e-27 0.000000e+00 8.145282e-02
9 1.017433e-27 0.000000e+00 8.145282e-02
10 1.017433e-27 0.000000e+00 8.145282e-02
11 1.017433e-27 0.000000e+00 8.145282e-02
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_11704\3768060156.py:41: FutureWarning:
The behavior of DataFrame concatenation with empty or all-NA entries is deprecated. In a future version, this will no longer exclude empty or all-NA columns when determining the result dtypes. To retain the old behavior, exclude the relevant entries before the concat operation.
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_11704\1254920799.py:16: RuntimeWarning:
divide by zero encountered in divide
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_11704\1254920799.py:16: RuntimeWarning:
divide by zero encountered in divide
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_11704\1254920799.py:16: RuntimeWarning:
divide by zero encountered in divide
C:\Users\Maestriadatos\AppData\Local\Temp\ipykernel_11704\1254920799.py:16: RuntimeWarning:
divide by zero encountered in divide
Variable |
Ventana |
MAPE |
MAE |
RMSE |
MSE |
R2 |
Ljung-Box p-value |
Jarque-Bera p-value |
|---|---|---|---|---|---|---|---|---|
Price |
7 |
1.263425e+05 |
126.342515 |
145.068566 |
21044.888928 |
-1.092713e+38 |
3.492455e-15 |
0.427166 |
Price |
14 |
1.263425e+05 |
126.342515 |
145.068566 |
21044.888928 |
-1.092713e+38 |
3.492455e-15 |
0.427166 |
Price |
21 |
1.263425e+05 |
126.342515 |
145.068566 |
21044.888928 |
-1.092713e+38 |
3.492455e-15 |
0.427166 |
Price |
28 |
1.263425e+05 |
126.342515 |
145.068566 |
21044.888928 |
-1.092713e+38 |
3.492455e-15 |
0.427166 |
Volatilidad_14 |
7 |
inf |
0.001476 |
0.001725 |
0.000003 |
0.000000e+00 |
4.114540e-15 |
0.431674 |
Volatilidad_14 |
14 |
inf |
0.001476 |
0.001725 |
0.000003 |
0.000000e+00 |
4.114540e-15 |
0.431674 |
Volatilidad_14 |
21 |
inf |
0.001476 |
0.001725 |
0.000003 |
0.000000e+00 |
4.114540e-15 |
0.431674 |
Volatilidad_14 |
28 |
inf |
0.001476 |
0.001725 |
0.000003 |
0.000000e+00 |
4.114540e-15 |
0.431674 |
At |
7 |
4.805572e-01 |
0.020774 |
0.023659 |
0.000560 |
0.000000e+00 |
3.974195e-15 |
0.431853 |
At |
14 |
4.805572e-01 |
0.020774 |
0.023659 |
0.000560 |
0.000000e+00 |
3.974195e-15 |
0.431853 |
At |
21 |
4.805572e-01 |
0.020774 |
0.023659 |
0.000560 |
0.000000e+00 |
3.974195e-15 |
0.431853 |
At |
28 |
4.805572e-01 |
0.020774 |
0.023659 |
0.000560 |
0.000000e+00 |
3.974195e-15 |
0.431853 |
Al analizar los residuos del modelo ARIMA sin rolling para las variables Price, Volatilidad_14 y At en diferentes ventanas, se observa un comportamiento notable en las métricas de error y en las pruebas de autocorrelación y normalidad.
Para la variable Price, el MAPE muestra valores extremadamente altos (del orden de 1 0 5 10 5 ), indicando un error porcentual significativo. Las métricas MAE y RMSE también reflejan que los residuos presentan un error considerable. El MSE de más de 21044 y el R² negativo (- 1 0 38 10 38 ) sugieren que el modelo no está logrando capturar adecuadamente la variabilidad de la serie temporal en ninguna de las ventanas. Además, la prueba de Ljung-Box muestra un valor p muy bajo ( 3.49 × 1 0 − 15 3.49×10 −15 ), lo que indica autocorrelación significativa en los residuos. Sin embargo, la prueba de Jarque-Bera arroja un p-valor de 0.427, lo que sugiere que, pese a la autocorrelación, los residuos no se desvían notablemente de la normalidad.
En el caso de Volatilidad_14, el MAPE es infinito en todas las ventanas, probablemente debido a valores residuales extremadamente bajos que generan problemas al calcular el error porcentual. Aunque el MAE y RMSE son mínimos (0.0015 y 0.0017 respectivamente), el R² de cero indica que el modelo no logra explicar ninguna variación en los datos. Al igual que con la variable Price, los residuos de Volatilidad_14 presentan una autocorrelación significativa (p-valor de Ljung-Box de 4.11 × 1 0 − 15 4.11×10 −15 ), y el p-valor de Jarque-Bera (0.431) sugiere normalidad de los residuos.
Para At, el MAPE es bastante bajo (aproximadamente 0.48), lo que indica que el modelo tiene una precisión razonable para esta variable en términos de error porcentual. Sin embargo, las métricas de error absoluto y cuadrático (MAE y RMSE) junto con el R² de cero sugieren que el modelo aún no es capaz de explicar la variabilidad de At. Nuevamente, la prueba de Ljung-Box da un p-valor muy bajo ( 3.97 × 1 0 − 15 3.97×10 −15 ), lo que indica autocorrelación significativa, y la prueba de Jarque-Bera con p-valor de 0.431 muestra una tendencia hacia la normalidad en los residuos.
En resumen, estos residuos presentan autocorrelación en todas las variables y ventanas, lo que indica una limitación en el modelo ARIMA sin rolling para capturar la dinámica de estas series. Además, aunque los residuos son en su mayoría normales, la capacidad predictiva del modelo es baja, especialmente en Price y Volatilidad_14, mientras que At muestra un desempeño ligeramente mejor en términos de precisión porcentual. Estos hallazgos sugieren que se podría explorar un ajuste adicional del modelo o un enfoque alternativo para mejorar la captura de la dinámica de las series.
Analisis grafico#
Al analizar los residuos del modelo ARIMA sin rolling para las variables Price, Volatilidad_14 y At en diferentes ventanas, se observa un comportamiento notable en las métricas de error y en las pruebas de autocorrelación y normalidad.
Para la variable Price, el MAPE muestra valores extremadamente altos (del orden de 1 0 5 10 5 ), indicando un error porcentual significativo. Las métricas MAE y RMSE también reflejan que los residuos presentan un error considerable. El MSE de más de 21044 y el R² negativo (- 1 0 38 10 38 ) sugieren que el modelo no está logrando capturar adecuadamente la variabilidad de la serie temporal en ninguna de las ventanas. Además, la prueba de Ljung-Box muestra un valor p muy bajo ( 3.49 × 1 0 − 15 3.49×10 −15 ), lo que indica autocorrelación significativa en los residuos. Sin embargo, la prueba de Jarque-Bera arroja un p-valor de 0.427, lo que sugiere que, pese a la autocorrelación, los residuos no se desvían notablemente de la normalidad.
En el caso de Volatilidad_14, el MAPE es infinito en todas las ventanas, probablemente debido a valores residuales extremadamente bajos que generan problemas al calcular el error porcentual. Aunque el MAE y RMSE son mínimos (0.0015 y 0.0017 respectivamente), el R² de cero indica que el modelo no logra explicar ninguna variación en los datos. Al igual que con la variable Price, los residuos de Volatilidad_14 presentan una autocorrelación significativa (p-valor de Ljung-Box de 4.11 × 1 0 − 15 4.11×10 −15 ), y el p-valor de Jarque-Bera (0.431) sugiere normalidad de los residuos.
Para At, el MAPE es bastante bajo (aproximadamente 0.48), lo que indica que el modelo tiene una precisión razonable para esta variable en términos de error porcentual. Sin embargo, las métricas de error absoluto y cuadrático (MAE y RMSE) junto con el R² de cero sugieren que el modelo aún no es capaz de explicar la variabilidad de At. Nuevamente, la prueba de Ljung-Box da un p-valor muy bajo ( 3.97 × 1 0 − 15 3.97×10 −15 ), lo que indica autocorrelación significativa, y la prueba de Jarque-Bera con p-valor de 0.431 muestra una tendencia hacia la normalidad en los residuos.
En resumen, estos residuos presentan autocorrelación en todas las variables y ventanas, lo que indica una limitación en el modelo ARIMA sin rolling para capturar la dinámica de estas series. Además, aunque los residuos son en su mayoría normales, la capacidad predictiva del modelo es baja, especialmente en Price y Volatilidad_14, mientras que At muestra un desempeño ligeramente mejor en términos de precisión porcentual. Estos hallazgos sugieren que se podría explorar un ajuste adicional del modelo o un enfoque alternativo para mejorar la captura de la dinámica de las series.
Garch con Rolling#
import pandas as pd import numpy as np from arch import arch_model from concurrent.futures import ThreadPoolExecutor import warnings
Suprimir warnings#
warnings.filterwarnings(“ignore”)
Parámetros generales#
ventanas = [14, 28, 21, 7] # Ventanas de rolling para todas las variables step_size = 20 # Tamaño de paso para el rolling train_size = len(df) - 28 test_size = 28
Función para ejecutar el rolling de GARCH en paralelo#
def ajustar_garch(variable, ventana, start, train_series): end = start + ventana rolling_train_series = train_series[start:end]
# Ajustar el modelo GARCH(1,1)
model = arch_model(rolling_train_series, vol='Garch', p=3, q=6)
model_fit = model.fit(disp=False, tol=1e-4) # Ajustar el modelo
# Realizar predicciones
forecast = model_fit.forecast(horizon=test_size)
var_pred = forecast.variance.values[-1, :] # Predicciones de varianza
# Guardar predicciones y resultados del modelo
return start, var_pred, model_fit.params, model_fit.loglikelihood, model_fit.aic, model_fit.bic
Función para realizar el ajuste de GARCH para una variable#
def procesar_variable(variable): predicciones = {} resultados_modelo = {}
# Tomar la serie de entrenamiento para cada variable
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
for ventana in ventanas:
predicciones[ventana] = {}
resultados_modelo[ventana] = {}
# Ejecutar rolling con paso fijo
with ThreadPoolExecutor() as executor:
futures = []
for start in range(0, len(train_series) - ventana, step_size): # Usamos step_size para saltar puntos
futures.append(executor.submit(ajustar_garch, variable, ventana, start, train_series))
# Obtener resultados de tareas paralelas
for future in futures:
start, var_pred, params, loglikelihood, aic, bic = future.result()
# Guardar predicciones
predicciones[ventana][start] = var_pred
# Almacenar los resultados del modelo (parámetros ajustados, AIC, BIC)
resultados_modelo[ventana][start] = {
'params': params,
'loglikelihood': loglikelihood,
'aic': aic,
'bic': bic
}
return predicciones, resultados_modelo
Procesar cada variable por separado#
print(“Procesando variable: Price”) predicciones_price, resultados_modelo_price = procesar_variable(‘Price’)
print(“Procesando variable: Volatilidad_14”) predicciones_volatilidad, resultados_modelo_volatilidad = procesar_variable(‘Volatilidad_14’)
print(“Procesando variable: At”) predicciones_at, resultados_modelo_at = procesar_variable(‘At’)
Imprimir o almacenar resultados finales por variable#
print(“Predicciones Price:”, predicciones_price) print(“Resultados Modelo Price:”, resultados_modelo_price)
print(“Predicciones Volatilidad_14:”, predicciones_volatilidad) print(“Resultados Modelo Volatilidad_14:”, resultados_modelo_volatilidad)
print(“Predicciones At:”, predicciones_at) print(“Resultados Modelo At:”, resultados_modelo_at)
import numpy as np
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
# Función para calcular el MAPE
def MAPE(y_true, y_pred):
return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
# Función para evaluar las métricas en el último set de predicciones de cada ventana
def evaluar_ultima_prediccion(variable, predicciones, test_series):
resultados_metricas = {}
for ventana, predicciones_por_ventana in predicciones.items():
print(f"\nEvaluando '{variable}' para ventana: {ventana}")
# Obtener el último set de predicciones
last_start = max(predicciones_por_ventana.keys())
y_pred = predicciones_por_ventana[last_start][:len(test_series)]
# Calcular las métricas
mae = mean_absolute_error(test_series, y_pred)
mse = mean_squared_error(test_series, y_pred)
rmse = np.sqrt(mse)
mape = MAPE(test_series, y_pred)
r2 = r2_score(test_series, y_pred)
# Guardar y mostrar los resultados de las métricas
resultados_metricas[ventana] = {
'MAE': mae,
'MSE': mse,
'RMSE': rmse,
'MAPE': mape,
'R2': r2
}
print(f" MAE: {mae:.4f}")
print(f" MSE: {mse:.4f}")
print(f" RMSE: {rmse:.4f}")
print(f" MAPE: {mape:.2f}%")
print(f" R^2: {r2:.4f}")
return resultados_metricas
# Conjunto de validación real para cada variable
test_size = 28
test_series_price = df['Price'].iloc[-test_size:].dropna().reset_index(drop=True)
test_series_volatilidad = df['Volatilidad_14'].iloc[-test_size:].dropna().reset_index(drop=True)
test_series_at = df['At'].iloc[-test_size:].dropna().reset_index(drop=True)
# 1. Evaluar y mostrar resultados para 'Price'
print("Evaluando modelo para 'Price'")
resultados_metricas_price = evaluar_ultima_prediccion('Price', predicciones_price, test_series_price)
# Los resultados métricas para cada variable están guardados en
# resultados_metricas_price, resultados_metricas_volatilidad y resultados_metricas_at
Evaluando modelo para 'Price'
Evaluando 'Price' para ventana: 14
MAE: 0.1000
MSE: 0.0100
RMSE: 0.1000
MAPE: 100.00%
R^2: -51922968585348295380567383343104.0000
Evaluando 'Price' para ventana: 28
MAE: 0.1000
MSE: 0.0100
RMSE: 0.1000
MAPE: 100.00%
R^2: -51922968585348295380567383343104.0000
Evaluando 'Price' para ventana: 21
MAE: 0.1000
MSE: 0.0100
RMSE: 0.1000
MAPE: 100.00%
R^2: -51922968585348295380567383343104.0000
Evaluando 'Price' para ventana: 7
MAE: 0.1000
MSE: 0.0100
RMSE: 0.1000
MAPE: 100.00%
R^2: -51922968585348295380567383343104.0000
# 2. Evaluar y mostrar resultados para 'Volatilidad_14'
print("\nEvaluando modelo para 'Volatilidad_14'")
resultados_metricas_volatilidad = evaluar_ultima_prediccion('Volatilidad_14', predicciones_volatilidad, test_series_volatilidad)
# 3. Evaluar y mostrar resultados para 'At'
print("\nEvaluando modelo para 'At'")
resultados_metricas_at = evaluar_ultima_prediccion('At', predicciones_at, test_series_at)
Evaluando modelo para 'Volatilidad_14'
Evaluando 'Volatilidad_14' para ventana: 14
MAE: 0.0000
MSE: 0.0000
RMSE: 0.0000
MAPE: nan%
R^2: 1.0000
Evaluando 'Volatilidad_14' para ventana: 28
MAE: 0.0000
MSE: 0.0000
RMSE: 0.0000
MAPE: nan%
R^2: 1.0000
Evaluando 'Volatilidad_14' para ventana: 21
MAE: 0.0000
MSE: 0.0000
RMSE: 0.0000
MAPE: nan%
R^2: 1.0000
Evaluando 'Volatilidad_14' para ventana: 7
MAE: 0.0000
MSE: 0.0000
RMSE: 0.0000
MAPE: nan%
R^2: 1.0000
Evaluando modelo para 'At'
Evaluando 'At' para ventana: 14
MAE: 4.3230
MSE: 18.6881
RMSE: 4.3230
MAPE: 100.00%
R^2: 0.0000
Evaluando 'At' para ventana: 28
MAE: 4.3230
MSE: 18.6881
RMSE: 4.3230
MAPE: 100.00%
R^2: 0.0000
Evaluando 'At' para ventana: 21
MAE: 4.3230
MSE: 18.6881
RMSE: 4.3230
MAPE: 100.00%
R^2: 0.0000
Evaluando 'At' para ventana: 7
MAE: 4.3230
MSE: 18.6881
RMSE: 4.3230
MAPE: 100.00%
R^2: 0.0000
# 3. Evaluar y mostrar resultados para 'At'
print("\nEvaluando modelo para 'At'")
resultados_metricas_at = evaluar_ultima_prediccion('At', predicciones_at, test_series_at)
Evaluando modelo para 'At'
Evaluando 'At' para ventana: 14
MAE: 4.3230
MSE: 18.6881
RMSE: 4.3230
MAPE: 100.00%
R^2: 0.0000
Evaluando 'At' para ventana: 28
MAE: 4.3230
MSE: 18.6881
RMSE: 4.3230
MAPE: 100.00%
R^2: 0.0000
Evaluando 'At' para ventana: 21
MAE: 4.3230
MSE: 18.6881
RMSE: 4.3230
MAPE: 100.00%
R^2: 0.0000
Evaluando 'At' para ventana: 7
MAE: 4.3230
MSE: 18.6881
RMSE: 4.3230
MAPE: 100.00%
R^2: 0.0000
import pandas as pd
import numpy as np
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from statsmodels.stats.diagnostic import acorr_ljungbox
from statsmodels.stats.stattools import jarque_bera
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm
# Función para calcular el MAPE
def MAPE(y_true, y_pred):
return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
# Función para evaluar el modelo y calcular métricas
def evaluar_modelo(variable, predicciones, test_series):
resultados_metricas = {}
for ventana, predicciones_por_ventana in predicciones.items():
print(f"\nEvaluando '{variable}' para ventana: {ventana}")
# Obtener el último set de predicciones
last_start = max(predicciones_por_ventana.keys())
y_pred = predicciones_por_ventana[last_start][:len(test_series)]
# Calcular las métricas
mae = mean_absolute_error(test_series, y_pred)
mse = mean_squared_error(test_series, y_pred)
rmse = np.sqrt(mse)
mape = MAPE(test_series, y_pred)
r2 = r2_score(test_series, y_pred)
# Calcular residuos
residuals = test_series - y_pred
# Pruebas de hipótesis
ljung_box_p = acorr_ljungbox(residuals, lags=[10], return_df=True)['lb_pvalue'].values[0]
jarque_bera_p = jarque_bera(residuals)[1]
# Guardar resultados
resultados_metricas[ventana] = {
'MAE': mae,
'MSE': mse,
'RMSE': rmse,
'MAPE': mape,
'R2': r2,
'Ljung-Box p-value': ljung_box_p,
'Jarque-Bera p-value': jarque_bera_p
}
print(f" MAE: {mae:.4f}")
print(f" MSE: {mse:.4f}")
print(f" RMSE: {rmse:.4f}")
print(f" MAPE: {mape:.2f}%")
print(f" R^2: {r2:.4f}")
print(f" Ljung-Box p-value: {ljung_box_p:.4f}")
print(f" Jarque-Bera p-value: {jarque_bera_p:.4f}")
# Visualización
plt.figure(figsize=(14, 5))
# Serie de residuos
plt.subplot(1, 3, 1)
plt.plot(residuals)
plt.axhline(0, color='red', linestyle='--', lw=2)
plt.title('Serie de residuos')
plt.xlabel('Índice')
plt.ylabel('Residuos')
# QQ Plot
plt.subplot(1, 3, 2)
sm.qqplot(residuals, line='s', ax=plt.gca())
plt.title('QQ Plot')
# ACF de residuos
plt.subplot(1, 3, 3)
from statsmodels.graphics.tsaplots import plot_acf
plot_acf(residuals, lags=20, ax=plt.gca())
plt.title('ACF de residuos')
plt.tight_layout()
plt.show()
# Convertir resultados a DataFrame
df_resultados = pd.DataFrame.from_dict(resultados_metricas, orient='index')
return df_resultados
# Evaluar modelos para cada variable
resultados_price = evaluar_modelo('Price', predicciones_price, test_series_price)
resultados_volatilidad = evaluar_modelo('Volatilidad_14', predicciones_volatilidad, test_series_volatilidad)
resultados_at = evaluar_modelo('At', predicciones_at, test_series_at)
# Concatenar resultados en un solo DataFrame
resultados_finales = pd.concat([resultados_price, resultados_volatilidad, resultados_at], keys=['Price', 'Volatilidad_14', 'At'])
print(resultados_finales)
Evaluando 'Price' para ventana: 14
MAE: 0.1000
MSE: 0.0100
RMSE: 0.1000
MAPE: 100.00%
R^2: -51922968585348295380567383343104.0000
Ljung-Box p-value: 0.0000
Jarque-Bera p-value: nan
Evaluando 'Price' para ventana: 28
MAE: 0.1000
MSE: 0.0100
RMSE: 0.1000
MAPE: 100.00%
R^2: -51922968585348295380567383343104.0000
Ljung-Box p-value: 0.0000
Jarque-Bera p-value: nan
Evaluando 'Price' para ventana: 21
MAE: 0.1000
MSE: 0.0100
RMSE: 0.1000
MAPE: 100.00%
R^2: -51922968585348295380567383343104.0000
Ljung-Box p-value: 0.0000
Jarque-Bera p-value: nan
Evaluando 'Price' para ventana: 7
MAE: 0.1000
MSE: 0.0100
RMSE: 0.1000
MAPE: 100.00%
R^2: -51922968585348295380567383343104.0000
Ljung-Box p-value: 0.0000
Jarque-Bera p-value: nan
Evaluando 'Volatilidad_14' para ventana: 14
MAE: 0.0000
MSE: 0.0000
RMSE: 0.0000
MAPE: nan%
R^2: 1.0000
Ljung-Box p-value: nan
Jarque-Bera p-value: nan
Evaluando 'Volatilidad_14' para ventana: 28
MAE: 0.0000
MSE: 0.0000
RMSE: 0.0000
MAPE: nan%
R^2: 1.0000
Ljung-Box p-value: nan
Jarque-Bera p-value: nan
Evaluando 'Volatilidad_14' para ventana: 21
MAE: 0.0000
MSE: 0.0000
RMSE: 0.0000
MAPE: nan%
R^2: 1.0000
Ljung-Box p-value: nan
Jarque-Bera p-value: nan
Evaluando 'Volatilidad_14' para ventana: 7
MAE: 0.0000
MSE: 0.0000
RMSE: 0.0000
MAPE: nan%
R^2: 1.0000
Ljung-Box p-value: nan
Jarque-Bera p-value: nan
Evaluando 'At' para ventana: 14
MAE: 4.3230
MSE: 18.6881
RMSE: 4.3230
MAPE: 100.00%
R^2: 0.0000
Ljung-Box p-value: nan
Jarque-Bera p-value: nan
Evaluando 'At' para ventana: 28
MAE: 4.3230
MSE: 18.6881
RMSE: 4.3230
MAPE: 100.00%
R^2: 0.0000
Ljung-Box p-value: nan
Jarque-Bera p-value: nan
Evaluando 'At' para ventana: 21
MAE: 4.3230
MSE: 18.6881
RMSE: 4.3230
MAPE: 100.00%
R^2: 0.0000
Ljung-Box p-value: nan
Jarque-Bera p-value: nan
Evaluando 'At' para ventana: 7
MAE: 4.3230
MSE: 18.6881
RMSE: 4.3230
MAPE: 100.00%
R^2: 0.0000
Ljung-Box p-value: nan
Jarque-Bera p-value: nan
MAE MSE RMSE MAPE R2 \
Price 14 0.100000 0.01000 0.100000 100.0 -5.192297e+31
28 0.100000 0.01000 0.100000 100.0 -5.192297e+31
21 0.100000 0.01000 0.100000 100.0 -5.192297e+31
7 0.100000 0.01000 0.100000 100.0 -5.192297e+31
Volatilidad_14 14 0.000000 0.00000 0.000000 NaN 1.000000e+00
28 0.000000 0.00000 0.000000 NaN 1.000000e+00
21 0.000000 0.00000 0.000000 NaN 1.000000e+00
7 0.000000 0.00000 0.000000 NaN 1.000000e+00
At 14 4.322978 18.68814 4.322978 100.0 0.000000e+00
28 4.322978 18.68814 4.322978 100.0 0.000000e+00
21 4.322978 18.68814 4.322978 100.0 0.000000e+00
7 4.322978 18.68814 4.322978 100.0 0.000000e+00
Ljung-Box p-value Jarque-Bera p-value
Price 14 4.081193e-46 NaN
28 4.081193e-46 NaN
21 4.081193e-46 NaN
7 4.081193e-46 NaN
Volatilidad_14 14 NaN NaN
28 NaN NaN
21 NaN NaN
7 NaN NaN
At 14 NaN NaN
28 NaN NaN
21 NaN NaN
7 NaN NaN
import pandas as pd
import numpy as np
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from statsmodels.stats.diagnostic import acorr_ljungbox
# Función para calcular el MAPE
def MAPE(y_true, y_pred):
return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
# Función para evaluar los modelos y construir la tabla de resultados
def evaluar_modelos_y_construir_tabla(predicciones_dict, test_series_dict):
resultados_tabla = []
for variable, predicciones in predicciones_dict.items():
test_series = test_series_dict[variable]
print(f"\nEvaluando la variable: {variable}") # Impresión para seguimiento
for ventana, predicciones_por_ventana in predicciones.items():
print(f"Evaluando para ventana: {ventana}")
# Obtener el último set de predicciones
last_start = max(predicciones_por_ventana.keys())
y_pred = predicciones_por_ventana[last_start][:len(test_series)]
# Imprimir la longitud de las series para verificar
print(f"Longitud de predicciones: {len(y_pred)}, Longitud de serie de prueba: {len(test_series)}")
# Verificar que y_pred tenga la longitud correcta
if len(y_pred) != len(test_series):
print(f"Error: La longitud de las predicciones ({len(y_pred)}) no coincide con la longitud de la serie de prueba ({len(test_series)})")
continue
# Calcular las métricas
mae = mean_absolute_error(test_series, y_pred)
mse = mean_squared_error(test_series, y_pred)
rmse = np.sqrt(mse)
mape = MAPE(test_series, y_pred)
r2 = r2_score(test_series, y_pred)
# Imprimir las métricas calculadas
print(f" MAE: {mae:.4f}, MSE: {mse:.4f}, RMSE: {rmse:.4f}, MAPE: {mape:.2f}%, R^2: {r2:.4f}")
# Calcular residuos y prueba de Ljung-Box
residuals = test_series - y_pred
ljung_box_result = acorr_ljungbox(residuals, lags=[10], return_df=True)
ljung_box_p = ljung_box_result['lb_pvalue'].values[0] if not ljung_box_result.empty else None
# Agregar resultados a la tabla
resultados_tabla.append({
'Variable': variable,
'Ventana': ventana,
'MAPE': mape,
'MAE': mae,
'RMSE': rmse,
'MSE': mse,
'R2': r2,
'Ljung-Box p-value': ljung_box_p
})
# Crear DataFrame con los resultados
df_resultados = pd.DataFrame(resultados_tabla)
return df_resultados
# Diccionario de predicciones y series de test
predicciones_dict = {
'Price': predicciones_price,
'Volatilidad_14': predicciones_volatilidad,
'At': predicciones_at
}
test_series_dict = {
'Price': test_series_price,
'Volatilidad_14': test_series_volatilidad,
'At': test_series_at
}
# Evaluar modelos y construir la tabla de resultados
resultados_finales = evaluar_modelos_y_construir_tabla(predicciones_dict, test_series_dict)
# Mostrar la tabla de resultados
if not resultados_finales.empty:
print(resultados_finales)
else:
print("No se generaron resultados.")
Evaluando la variable: Price
Evaluando para ventana: 14
Longitud de predicciones: 28, Longitud de serie de prueba: 28
MAE: 0.1000, MSE: 0.0100, RMSE: 0.1000, MAPE: 100.00%, R^2: -51922968585348295380567383343104.0000
Evaluando para ventana: 28
Longitud de predicciones: 28, Longitud de serie de prueba: 28
MAE: 0.1000, MSE: 0.0100, RMSE: 0.1000, MAPE: 100.00%, R^2: -51922968585348295380567383343104.0000
Evaluando para ventana: 21
Longitud de predicciones: 28, Longitud de serie de prueba: 28
MAE: 0.1000, MSE: 0.0100, RMSE: 0.1000, MAPE: 100.00%, R^2: -51922968585348295380567383343104.0000
Evaluando para ventana: 7
Longitud de predicciones: 28, Longitud de serie de prueba: 28
MAE: 0.1000, MSE: 0.0100, RMSE: 0.1000, MAPE: 100.00%, R^2: -51922968585348295380567383343104.0000
Evaluando la variable: Volatilidad_14
Evaluando para ventana: 14
Longitud de predicciones: 28, Longitud de serie de prueba: 28
MAE: 0.0000, MSE: 0.0000, RMSE: 0.0000, MAPE: nan%, R^2: 1.0000
Evaluando para ventana: 28
Longitud de predicciones: 28, Longitud de serie de prueba: 28
MAE: 0.0000, MSE: 0.0000, RMSE: 0.0000, MAPE: nan%, R^2: 1.0000
Evaluando para ventana: 21
Longitud de predicciones: 28, Longitud de serie de prueba: 28
MAE: 0.0000, MSE: 0.0000, RMSE: 0.0000, MAPE: nan%, R^2: 1.0000
Evaluando para ventana: 7
Longitud de predicciones: 28, Longitud de serie de prueba: 28
MAE: 0.0000, MSE: 0.0000, RMSE: 0.0000, MAPE: nan%, R^2: 1.0000
Evaluando la variable: At
Evaluando para ventana: 14
Longitud de predicciones: 28, Longitud de serie de prueba: 28
MAE: 4.3230, MSE: 18.6881, RMSE: 4.3230, MAPE: 100.00%, R^2: 0.0000
Evaluando para ventana: 28
Longitud de predicciones: 28, Longitud de serie de prueba: 28
MAE: 4.3230, MSE: 18.6881, RMSE: 4.3230, MAPE: 100.00%, R^2: 0.0000
Evaluando para ventana: 21
Longitud de predicciones: 28, Longitud de serie de prueba: 28
MAE: 4.3230, MSE: 18.6881, RMSE: 4.3230, MAPE: 100.00%, R^2: 0.0000
Evaluando para ventana: 7
Longitud de predicciones: 28, Longitud de serie de prueba: 28
MAE: 4.3230, MSE: 18.6881, RMSE: 4.3230, MAPE: 100.00%, R^2: 0.0000
Variable Ventana MAPE MAE RMSE MSE \
0 Price 14 100.0 0.100000 0.100000 0.01000
1 Price 28 100.0 0.100000 0.100000 0.01000
2 Price 21 100.0 0.100000 0.100000 0.01000
3 Price 7 100.0 0.100000 0.100000 0.01000
4 Volatilidad_14 14 NaN 0.000000 0.000000 0.00000
5 Volatilidad_14 28 NaN 0.000000 0.000000 0.00000
6 Volatilidad_14 21 NaN 0.000000 0.000000 0.00000
7 Volatilidad_14 7 NaN 0.000000 0.000000 0.00000
8 At 14 100.0 4.322978 4.322978 18.68814
9 At 28 100.0 4.322978 4.322978 18.68814
10 At 21 100.0 4.322978 4.322978 18.68814
11 At 7 100.0 4.322978 4.322978 18.68814
R2 Ljung-Box p-value
0 -5.192297e+31 4.081193e-46
1 -5.192297e+31 4.081193e-46
2 -5.192297e+31 4.081193e-46
3 -5.192297e+31 4.081193e-46
4 1.000000e+00 NaN
5 1.000000e+00 NaN
6 1.000000e+00 NaN
7 1.000000e+00 NaN
8 0.000000e+00 NaN
9 0.000000e+00 NaN
10 0.000000e+00 NaN
11 0.000000e+00 NaN
import pandas as pd
import numpy as np
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from statsmodels.stats.diagnostic import acorr_ljungbox
# Función para calcular el MAPE
def MAPE(y_true, y_pred):
return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
# Función para evaluar la variable "At"
def evaluar_variable_at(predicciones_at, test_series_at):
resultados_tabla = []
print("Evaluando la variable: At")
for ventana, predicciones_por_ventana in predicciones_at.items():
print(f"Evaluando para ventana: {ventana}")
# Obtener el último set de predicciones
last_start = max(predicciones_por_ventana.keys())
y_pred = predicciones_por_ventana[last_start][:len(test_series_at)]
# Imprimir la longitud de las series para verificar
print(f"Longitud de predicciones: {len(y_pred)}, Longitud de serie de prueba: {len(test_series_at)}")
# Verificar que y_pred tenga la longitud correcta
if len(y_pred) != len(test_series_at):
print(f"Error: La longitud de las predicciones ({len(y_pred)}) no coincide con la longitud de la serie de prueba ({len(test_series_at)})")
continue
# Calcular las métricas
mae = mean_absolute_error(test_series_at, y_pred)
mse = mean_squared_error(test_series_at, y_pred)
rmse = np.sqrt(mse)
mape = MAPE(test_series_at, y_pred)
r2 = r2_score(test_series_at, y_pred)
# Imprimir las métricas calculadas
print(f" MAE: {mae:.4f}, MSE: {mse:.4f}, RMSE: {rmse:.4f}, MAPE: {mape:.2f}%, R^2: {r2:.4f}")
# Calcular residuos y prueba de Ljung-Box
residuals = test_series_at - y_pred
ljung_box_result = acorr_ljungbox(residuals, lags=[10], return_df=True)
ljung_box_p = ljung_box_result['lb_pvalue'].values[0] if not ljung_box_result.empty else None
# Agregar resultados a la tabla
resultados_tabla.append({
'Variable': 'At',
'Ventana': ventana,
'MAPE': mape,
'MAE': mae,
'RMSE': rmse,
'MSE': mse,
'R2': r2,
'Ljung-Box p-value': ljung_box_p
})
# Crear DataFrame con los resultados
df_resultados = pd.DataFrame(resultados_tabla)
return df_resultados
# Evaluar solo la variable 'At'
resultados_at = evaluar_variable_at(predicciones_at, test_series_at)
# Mostrar la tabla de resultados para 'At'
if not resultados_at.empty:
print("\nResultados para 'At':")
print(resultados_at)
else:
print("No se generaron resultados para 'At'.")
Evaluando la variable: At
Evaluando para ventana: 14
Longitud de predicciones: 28, Longitud de serie de prueba: 28
MAE: 4.3230, MSE: 18.6881, RMSE: 4.3230, MAPE: 100.00%, R^2: 0.0000
Evaluando para ventana: 28
Longitud de predicciones: 28, Longitud de serie de prueba: 28
MAE: 4.3230, MSE: 18.6881, RMSE: 4.3230, MAPE: 100.00%, R^2: 0.0000
Evaluando para ventana: 21
Longitud de predicciones: 28, Longitud de serie de prueba: 28
MAE: 4.3230, MSE: 18.6881, RMSE: 4.3230, MAPE: 100.00%, R^2: 0.0000
Evaluando para ventana: 7
Longitud de predicciones: 28, Longitud de serie de prueba: 28
MAE: 4.3230, MSE: 18.6881, RMSE: 4.3230, MAPE: 100.00%, R^2: 0.0000
Resultados para 'At':
Variable Ventana MAPE MAE RMSE MSE R2 \
0 At 14 100.0 4.322978 4.322978 18.68814 0.0
1 At 28 100.0 4.322978 4.322978 18.68814 0.0
2 At 21 100.0 4.322978 4.322978 18.68814 0.0
3 At 7 100.0 4.322978 4.322978 18.68814 0.0
Ljung-Box p-value
0 NaN
1 NaN
2 NaN
3 NaN
Resumen de lo obtenido en el Garch con rolling#
A pesar de implementar el modelo GARCH con una ventana móvil (rolling), no se lograron captar patrones significativos en las variables de precio, volatilidad y retornos acumulados del precio del bitcoin. Esto puede deberse a dos factores principales.
Primero, la selección de hiperparámetros del modelo GARCH es un proceso computacionalmente intensivo, y la capacidad computacional disponible pudo haber limitado la búsqueda exhaustiva de combinaciones óptimas en cada ventana. Esto resultó en un ajuste inadecuado a los datos específicos, afectando la precisión y la capacidad predictiva del modelo.
En segundo lugar, los modelos GARCH pueden ser inadecuados para datos extremadamente volátiles y no lineales, como los precios del bitcoin. Este tipo de modelo supone una estructura lineal en la varianza condicional, lo que puede no ser suficiente para capturar la complejidad de las series, caracterizadas por fluctuaciones abruptas y comportamientos no lineales.
Garch sin Rolling#
import pandas as pd
import numpy as np
from arch import arch_model
import warnings
# Suprimir warnings
warnings.filterwarnings("ignore")
# Parámetros generales
ventanas = [14, 28, 21, 7] # Ventanas para todas las variables
train_size = len(df) - 28 # Tamaño de entrenamiento
test_size = 28 # Tamaño de prueba
# Función para ajustar un modelo GARCH
def ajustar_garch(variable, ventana, train_series):
# Tomar la serie de entrenamiento con la ventana especificada
rolling_train_series = train_series.iloc[-ventana:] # Tomar los últimos 'ventana' puntos
# Ajustar el modelo GARCH(1,1)
model = arch_model(rolling_train_series, vol='Garch', p=1, q=1)
model_fit = model.fit(disp=False, tol=1e-4) # Ajustar el modelo
# Realizar predicciones
forecast = model_fit.forecast(horizon=test_size)
var_pred = forecast.variance.values[-1, :] # Predicciones de varianza
# Guardar resultados del modelo
return var_pred, model_fit.params, model_fit.loglikelihood, model_fit.aic, model_fit.bic
# Función para procesar cada variable
def procesar_variable(variable):
predicciones = {}
resultados_modelo = {}
# Tomar la serie de entrenamiento para cada variable
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
for ventana in ventanas:
# Ajustar GARCH para cada ventana y obtener resultados
var_pred, params, loglikelihood, aic, bic = ajustar_garch(variable, ventana, train_series)
# Almacenar predicciones y resultados del modelo
predicciones[ventana] = var_pred
resultados_modelo[ventana] = {
'params': params,
'loglikelihood': loglikelihood,
'aic': aic,
'bic': bic
}
return predicciones, resultados_modelo
# Procesar cada variable por separado
print("Procesando variable: Price")
predicciones_price, resultados_modelo_price = procesar_variable('Price')
print("Procesando variable: Volatilidad_14")
predicciones_volatilidad, resultados_modelo_volatilidad = procesar_variable('Volatilidad_14')
print("Procesando variable: At")
predicciones_at, resultados_modelo_at = procesar_variable('At')
# Imprimir o almacenar resultados finales por variable
print("Predicciones Price:", predicciones_price)
print("Resultados Modelo Price:", resultados_modelo_price)
print("Predicciones Volatilidad_14:", predicciones_volatilidad)
print("Resultados Modelo Volatilidad_14:", resultados_modelo_volatilidad)
print("Predicciones At:", predicciones_at)
print("Resultados Modelo At:", resultados_modelo_at)
Procesando variable: Price
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
Procesando variable: Volatilidad_14
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
Procesando variable: At
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
Predicciones Price: {14: array([1.92592994e-34, 1.92592994e-34, 1.92592994e-34, 1.92592994e-34,
1.92592994e-34, 1.92592994e-34, 1.92592994e-34, 1.92592994e-34,
1.92592994e-34, 1.92592994e-34, 1.92592994e-34, 1.92592994e-34,
1.92592994e-34, 1.92592994e-34, 1.92592994e-34, 1.92592994e-34,
1.92592994e-34, 1.92592994e-34, 1.92592994e-34, 1.92592994e-34,
1.92592994e-34, 1.92592994e-34, 1.92592994e-34, 1.92592994e-34,
1.92592994e-34, 1.92592994e-34, 1.92592994e-34, 1.92592994e-34]), 28: array([1.92592994e-34, 1.92592994e-34, 1.92592994e-34, 1.92592994e-34,
1.92592994e-34, 1.92592994e-34, 1.92592994e-34, 1.92592994e-34,
1.92592994e-34, 1.92592994e-34, 1.92592994e-34, 1.92592994e-34,
1.92592994e-34, 1.92592994e-34, 1.92592994e-34, 1.92592994e-34,
1.92592994e-34, 1.92592994e-34, 1.92592994e-34, 1.92592994e-34,
1.92592994e-34, 1.92592994e-34, 1.92592994e-34, 1.92592994e-34,
1.92592994e-34, 1.92592994e-34, 1.92592994e-34, 1.92592994e-34]), 21: array([1.73333695e-33, 1.73333695e-33, 1.73333695e-33, 1.73333695e-33,
1.73333695e-33, 1.73333695e-33, 1.73333695e-33, 1.73333695e-33,
1.73333695e-33, 1.73333695e-33, 1.73333695e-33, 1.73333695e-33,
1.73333695e-33, 1.73333695e-33, 1.73333695e-33, 1.73333695e-33,
1.73333695e-33, 1.73333695e-33, 1.73333695e-33, 1.73333695e-33,
1.73333695e-33, 1.73333695e-33, 1.73333695e-33, 1.73333695e-33,
1.73333695e-33, 1.73333695e-33, 1.73333695e-33, 1.73333695e-33]), 7: array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])}
Resultados Modelo Price: {14: {'params': mu 1.000000e-01
omega 9.629650e-35
alpha[1] 1.000000e-02
beta[1] 4.900000e-01
Name: params, dtype: float64, 'loglikelihood': 523.5622500941317, 'aic': -1039.1245001882635, 'bic': np.float64(-1036.5682708698025)}, 28: {'params': mu 1.000000e-01
omega 9.629650e-35
alpha[1] 1.000000e-02
beta[1] 4.900000e-01
Name: params, dtype: float64, 'loglikelihood': 1047.1245001882633, 'aic': -2086.2490003765265, 'bic': np.float64(-2080.9201823358258)}, 21: {'params': mu 1.000000e-01
omega 8.666685e-34
alpha[1] 1.000000e-02
beta[1] 4.900000e-01
Name: params, dtype: float64, 'loglikelihood': 762.2725170791673, 'aic': -1516.5450341583346, 'bic': np.float64(-1512.366944407441)}, 7: {'params': mu 0.10
omega 0.00
alpha[1] 0.01
beta[1] 0.49
Name: params, dtype: float64, 'loglikelihood': nan, 'aic': nan, 'bic': np.float64(nan)}}
Predicciones Volatilidad_14: {14: array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]), 28: array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]), 21: array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]), 7: array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])}
Resultados Modelo Volatilidad_14: {14: {'params': mu 0.00
omega 0.00
alpha[1] 0.01
beta[1] 0.49
Name: params, dtype: float64, 'loglikelihood': nan, 'aic': nan, 'bic': np.float64(nan)}, 28: {'params': mu 0.00
omega 0.00
alpha[1] 0.01
beta[1] 0.49
Name: params, dtype: float64, 'loglikelihood': nan, 'aic': nan, 'bic': np.float64(nan)}, 21: {'params': mu 0.00
omega 0.00
alpha[1] 0.01
beta[1] 0.49
Name: params, dtype: float64, 'loglikelihood': nan, 'aic': nan, 'bic': np.float64(nan)}, 7: {'params': mu 0.00
omega 0.00
alpha[1] 0.01
beta[1] 0.49
Name: params, dtype: float64, 'loglikelihood': nan, 'aic': nan, 'bic': np.float64(nan)}}
Predicciones At: {14: array([7.88860905e-31, 7.88860905e-31, 7.88860905e-31, 7.88860905e-31,
7.88860905e-31, 7.88860905e-31, 7.88860905e-31, 7.88860905e-31,
7.88860905e-31, 7.88860905e-31, 7.88860905e-31, 7.88860905e-31,
7.88860905e-31, 7.88860905e-31, 7.88860905e-31, 7.88860905e-31,
7.88860905e-31, 7.88860905e-31, 7.88860905e-31, 7.88860905e-31,
7.88860905e-31, 7.88860905e-31, 7.88860905e-31, 7.88860905e-31,
7.88860905e-31, 7.88860905e-31, 7.88860905e-31, 7.88860905e-31]), 28: array([7.88860905e-31, 7.88860905e-31, 7.88860905e-31, 7.88860905e-31,
7.88860905e-31, 7.88860905e-31, 7.88860905e-31, 7.88860905e-31,
7.88860905e-31, 7.88860905e-31, 7.88860905e-31, 7.88860905e-31,
7.88860905e-31, 7.88860905e-31, 7.88860905e-31, 7.88860905e-31,
7.88860905e-31, 7.88860905e-31, 7.88860905e-31, 7.88860905e-31,
7.88860905e-31, 7.88860905e-31, 7.88860905e-31, 7.88860905e-31,
7.88860905e-31, 7.88860905e-31, 7.88860905e-31, 7.88860905e-31]), 21: array([3.15544362e-30, 3.15544362e-30, 3.15544362e-30, 3.15544362e-30,
3.15544362e-30, 3.15544362e-30, 3.15544362e-30, 3.15544362e-30,
3.15544362e-30, 3.15544362e-30, 3.15544362e-30, 3.15544362e-30,
3.15544362e-30, 3.15544362e-30, 3.15544362e-30, 3.15544362e-30,
3.15544362e-30, 3.15544362e-30, 3.15544362e-30, 3.15544362e-30,
3.15544362e-30, 3.15544362e-30, 3.15544362e-30, 3.15544362e-30,
3.15544362e-30, 3.15544362e-30, 3.15544362e-30, 3.15544362e-30]), 7: array([7.88860905e-31, 7.88860905e-31, 7.88860905e-31, 7.88860905e-31,
7.88860905e-31, 7.88860905e-31, 7.88860905e-31, 7.88860905e-31,
7.88860905e-31, 7.88860905e-31, 7.88860905e-31, 7.88860905e-31,
7.88860905e-31, 7.88860905e-31, 7.88860905e-31, 7.88860905e-31,
7.88860905e-31, 7.88860905e-31, 7.88860905e-31, 7.88860905e-31,
7.88860905e-31, 7.88860905e-31, 7.88860905e-31, 7.88860905e-31,
7.88860905e-31, 7.88860905e-31, 7.88860905e-31, 7.88860905e-31])}
Resultados Modelo At: {14: {'params': mu -4.322978e+00
omega 3.944305e-31
alpha[1] 1.000000e-02
beta[1] 4.900000e-01
Name: params, dtype: float64, 'loglikelihood': 465.3378869270963, 'aic': -922.6757738541926, 'bic': np.float64(-920.1195445357315)}, 28: {'params': mu -4.322978e+00
omega 3.944305e-31
alpha[1] 1.000000e-02
beta[1] 4.900000e-01
Name: params, dtype: float64, 'loglikelihood': 930.6757738541924, 'aic': -1853.3515477083847, 'bic': np.float64(-1848.022729667684)}, 21: {'params': mu -4.322978e+00
omega 1.577722e-30
alpha[1] 1.000000e-02
beta[1] 4.900000e-01
Name: params, dtype: float64, 'loglikelihood': 683.4507395988855, 'aic': -1358.901479197771, 'bic': np.float64(-1354.7233894468775)}, 7: {'params': mu -4.322978e+00
omega 3.944305e-31
alpha[1] 1.000000e-02
beta[1] 4.900000e-01
Name: params, dtype: float64, 'loglikelihood': 232.66894346354815, 'aic': -457.3378869270963, 'bic': np.float64(-457.55424633087506)}}
import pandas as pd
import numpy as np
from arch import arch_model
import warnings
# Suprimir warnings
warnings.filterwarnings("ignore")
# Parámetros generales
ventanas = [14, 28, 21, 7] # Ventanas para todas las variables
train_size = len(df) - 28 # Tamaño de entrenamiento
test_size = 28 # Tamaño de prueba
# Función para ajustar un modelo GARCH
def ajustar_garch(variable, ventana, train_series):
# Tomar la serie de entrenamiento con la ventana especificada
rolling_train_series = train_series.iloc[-ventana:] # Tomar los últimos 'ventana' puntos
# Ajustar el modelo GARCH(1,1)
model = arch_model(rolling_train_series, vol='Garch', p=1, q=1)
model_fit = model.fit(disp=False, tol=1e-4) # Ajustar el modelo
# Realizar predicciones
forecast = model_fit.forecast(horizon=test_size)
var_pred = forecast.variance.values[-1, :] # Predicciones de varianza
# Guardar resultados del modelo
return var_pred, model_fit.params, model_fit.loglikelihood, model_fit.aic, model_fit.bic
# Función para calcular métricas
def calcular_metricas(predicciones, reales):
n = len(reales)
# Evitar división por cero en MAPE
mape = np.mean(np.abs((reales - predicciones) / reales)) * 100 if np.all(reales != 0) else np.nan
mae = np.mean(np.abs(reales - predicciones))
rmse = np.sqrt(np.mean((reales - predicciones) ** 2))
mse = np.mean((reales - predicciones) ** 2)
# Calcular R²
ss_res = np.sum((reales - predicciones) ** 2)
ss_tot = np.sum((reales - np.mean(reales)) ** 2)
r2 = 1 - (ss_res / ss_tot) if ss_tot != 0 else np.nan
return mape, mae, rmse, mse, r2
# Función para procesar cada variable
def procesar_variable(variable):
predicciones = {}
resultados_modelo = {}
metricas_resultados = []
# Tomar la serie de entrenamiento para cada variable
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
# Tomar la serie de prueba
test_series = df[variable].iloc[train_size:].dropna().reset_index(drop=True)
for ventana in ventanas:
# Ajustar GARCH para cada ventana y obtener resultados
var_pred, params, loglikelihood, aic, bic = ajustar_garch(variable, ventana, train_series)
# Almacenar predicciones y resultados del modelo
predicciones[ventana] = var_pred
resultados_modelo[ventana] = {
'params': params,
'loglikelihood': loglikelihood,
'aic': aic,
'bic': bic
}
# Calcular métricas
mape, mae, rmse, mse, r2 = calcular_metricas(var_pred, test_series.values)
metricas_resultados.append({
'Variable': variable,
'Ventana': ventana,
'MAPE': mape,
'MAE': mae,
'RMSE': rmse,
'MSE': mse,
'R²': r2
})
return predicciones, resultados_modelo, metricas_resultados
# Procesar cada variable por separado
print("Procesando variable: Price")
predicciones_price, resultados_modelo_price, metricas_price = procesar_variable('Price')
print("Procesando variable: Volatilidad_14")
predicciones_volatilidad, resultados_modelo_volatilidad, metricas_volatilidad = procesar_variable('Volatilidad_14')
print("Procesando variable: At")
predicciones_at, resultados_modelo_at, metricas_at = procesar_variable('At')
# Combinar resultados en un DataFrame
resultados_metricas = pd.DataFrame(metricas_price + metricas_volatilidad + metricas_at)
# Imprimir o almacenar resultados finales por variable
print(resultados_metricas)
Procesando variable: Price
Procesando variable: Volatilidad_14
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
Procesando variable: At
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
Variable Ventana MAPE MAE RMSE MSE R²
0 Price 14 100.0 0.100000 0.100000 0.01000 -5.192297e+31
1 Price 28 100.0 0.100000 0.100000 0.01000 -5.192297e+31
2 Price 21 100.0 0.100000 0.100000 0.01000 -5.192297e+31
3 Price 7 100.0 0.100000 0.100000 0.01000 -5.192297e+31
4 Volatilidad_14 14 NaN 0.000000 0.000000 0.00000 NaN
5 Volatilidad_14 28 NaN 0.000000 0.000000 0.00000 NaN
6 Volatilidad_14 21 NaN 0.000000 0.000000 0.00000 NaN
7 Volatilidad_14 7 NaN 0.000000 0.000000 0.00000 NaN
8 At 14 100.0 4.322978 4.322978 18.68814 NaN
9 At 28 100.0 4.322978 4.322978 18.68814 NaN
10 At 21 100.0 4.322978 4.322978 18.68814 NaN
11 At 7 100.0 4.322978 4.322978 18.68814 NaN
import pandas as pd
import numpy as np
from arch import arch_model
from statsmodels.stats.diagnostic import acorr_ljungbox
from statsmodels.stats.stattools import jarque_bera
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
# Suprimir warnings
warnings.filterwarnings("ignore")
# Parámetros generales
ventanas = [14, 28, 21, 7] # Ventanas para todas las variables
train_size = len(df) - 28 # Tamaño de entrenamiento
test_size = 28 # Tamaño de prueba
# Función para ajustar un modelo GARCH
def ajustar_garch(variable, train_series):
# Ajustar el modelo GARCH(1,1)
model = arch_model(train_series, vol='Garch', p=1, q=1)
model_fit = model.fit(disp=False, tol=1e-4) # Ajustar el modelo
# Obtener residuos
residuo = model_fit.resid
# Realizar predicciones (solo para referencia)
forecast = model_fit.forecast(horizon=test_size)
var_pred = forecast.variance.values[-1, :] # Predicciones de varianza
return residuo, model_fit
# Función para calcular métricas
def calcular_metricas(predicciones, reales):
n = len(reales)
# Evitar división por cero en MAPE
mape = np.mean(np.abs((reales - predicciones) / reales)) * 100 if np.all(reales != 0) else np.nan
mae = np.mean(np.abs(reales - predicciones))
rmse = np.sqrt(np.mean((reales - predicciones) ** 2))
mse = np.mean((reales - predicciones) ** 2)
# Calcular R²
ss_res = np.sum((reales - predicciones) ** 2)
ss_tot = np.sum((reales - np.mean(reales)) ** 2)
r2 = 1 - (ss_res / ss_tot) if ss_tot != 0 else np.nan
return mape, mae, rmse, mse, r2
# Función para procesar cada variable y calcular residuos
def procesar_variable(variable):
# Tomar la serie de entrenamiento para cada variable
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
# Ajustar GARCH y obtener residuos
residuo, model_fit = ajustar_garch(variable, train_series)
# Calcular métricas de residuos contra cero
mape, mae, rmse, mse, r2 = calcular_metricas(residuo, np.zeros(len(residuo)))
# Pruebas de hipótesis
ljung_box_results = acorr_ljungbox(residuo, lags=[10], return_df=True)
jarque_bera_results = jarque_bera(residuo)
# Crear dataframe para resultados
resultados_metricas = {
'MAPE': mape,
'MAE': mae,
'RMSE': rmse,
'MSE': mse,
'R²': r2,
'Ljung-Box test (p-value)': ljung_box_results['lb_pvalue'].iloc[0],
'Jarque-Bera (p-value)': jarque_bera_results[1],
}
# Crear tabla de resultados
resultados_df = pd.DataFrame([resultados_metricas])
# Graficar residuos, QQPlot y ACF
plt.figure(figsize=(15, 12))
# Serie de residuos
plt.subplot(3, 1, 1)
plt.plot(residuo)
plt.title(f'Serie de Residuos: {variable}')
# QQPlot
plt.subplot(3, 1, 2)
sns.set_style("whitegrid")
stats.probplot(residuo, dist="norm", plot=plt)
plt.title('QQPlot de Residuos')
# ACF de residuos
plt.subplot(3, 1, 3)
plt.acorr(residuo, maxlags=20)
plt.title('ACF de Residuos')
plt.tight_layout()
plt.show()
return resultados_df
# Procesar cada variable por separado
resultados_price = procesar_variable('Price')
resultados_volatilidad = procesar_variable('Volatilidad_14')
resultados_at = procesar_variable('At')
# Combinar resultados
resultados_completos = pd.concat([resultados_price, resultados_volatilidad, resultados_at], axis=0)
print(resultados_completos)
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
MAPE MAE RMSE MSE R² \
0 NaN 10623.182669 18948.945939 3.590626e+08 NaN
0 NaN 0.022875 0.044418 1.972921e-03 NaN
0 NaN 1.135355 1.487655 2.213117e+00 NaN
Ljung-Box test (p-value) Jarque-Bera (p-value)
0 0.0 0.000000e+00
0 0.0 0.000000e+00
0 0.0 1.968244e-63
import pandas as pd
import numpy as np
from arch import arch_model
from statsmodels.stats.diagnostic import acorr_ljungbox
import warnings
# Suprimir warnings
warnings.filterwarnings("ignore")
# Parámetros generales
ventanas = [14, 28, 21, 7] # Ventanas para todas las variables
train_size = len(df) - 28 # Tamaño de entrenamiento
test_size = 28 # Tamaño de prueba
# Función para ajustar un modelo GARCH
def ajustar_garch(variable, ventana, train_series):
rolling_train_series = train_series.iloc[-ventana:] # Tomar los últimos 'ventana' puntos
model = arch_model(rolling_train_series, vol='Garch', p=1, q=1)
model_fit = model.fit(disp=False, tol=1e-4) # Ajustar el modelo
forecast = model_fit.forecast(horizon=test_size)
var_pred = forecast.variance.values[-1, :] # Predicciones de varianza
return var_pred, model_fit.resid[-test_size:], model_fit.params, model_fit.loglikelihood, model_fit.aic, model_fit.bic
# Función para calcular métricas
def calcular_metricas(predicciones, reales):
n = len(reales)
# Evitar división por cero en MAPE
mape = np.mean(np.abs((reales - predicciones) / reales)) * 100 if np.all(reales != 0) else np.nan
mae = np.mean(np.abs(reales - predicciones))
rmse = np.sqrt(np.mean((reales - predicciones) ** 2))
mse = np.mean((reales - predicciones) ** 2)
# Calcular R²
ss_res = np.sum((reales - predicciones) ** 2)
ss_tot = np.sum((reales - np.mean(reales)) ** 2)
r2 = 1 - (ss_res / ss_tot) if ss_tot != 0 else np.nan
return mape, mae, rmse, mse, r2
# Función para procesar cada variable
def procesar_variable(variable):
metricas_resultados = []
train_series = df[variable].iloc[:train_size].dropna().reset_index(drop=True)
test_series = df[variable].iloc[train_size:].dropna().reset_index(drop=True)
for ventana in ventanas:
var_pred, residuo, params, loglikelihood, aic, bic = ajustar_garch(variable, ventana, train_series)
# Calcular métricas
mape, mae, rmse, mse, r2 = calcular_metricas(var_pred, test_series.values)
# Prueba de Ljung-Box
max_lags = min(10, len(residuo) - 1) # Asegurarse de que los lags sean menores que el tamaño de residuo
if max_lags > 0:
ljung_box_results = acorr_ljungbox(residuo, lags=[max_lags], return_df=True)
ljung_box_pvalue = ljung_box_results['lb_pvalue'].values[0]
else:
ljung_box_pvalue = np.nan # No hay suficientes datos para la prueba
metricas_resultados.append({
'Variable': variable,
'Ventana': ventana,
'MAPE': mape,
'MAE': mae,
'RMSE': rmse,
'MSE': mse,
'R²': r2,
'Ljung-Box p-value': ljung_box_pvalue
})
return metricas_resultados
# Procesar cada variable por separado
print("Procesando variable: Price")
metricas_price = procesar_variable('Price')
print("Procesando variable: Volatilidad_14")
metricas_volatilidad = procesar_variable('Volatilidad_14')
print("Procesando variable: At")
metricas_at = procesar_variable('At')
# Combinar resultados en un DataFrame
resultados_metricas = pd.DataFrame(metricas_price + metricas_volatilidad + metricas_at)
# Imprimir o almacenar resultados finales por variable
print(resultados_metricas)
Procesando variable: Price
Procesando variable: Volatilidad_14
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
Procesando variable: At
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
c:\Users\Maestriadatos\miniconda3\envs\ml_venv\lib\site-packages\arch\univariate\base.py:766: ConvergenceWarning:
The optimizer returned code 4. The message is:
Inequality constraints incompatible
See scipy.optimize.fmin_slsqp for code meaning.
Variable Ventana MAPE MAE RMSE MSE \
0 Price 14 100.0 0.100000 0.100000 0.01000
1 Price 28 100.0 0.100000 0.100000 0.01000
2 Price 21 100.0 0.100000 0.100000 0.01000
3 Price 7 100.0 0.100000 0.100000 0.01000
4 Volatilidad_14 14 NaN 0.000000 0.000000 0.00000
5 Volatilidad_14 28 NaN 0.000000 0.000000 0.00000
6 Volatilidad_14 21 NaN 0.000000 0.000000 0.00000
7 Volatilidad_14 7 NaN 0.000000 0.000000 0.00000
8 At 14 100.0 4.322978 4.322978 18.68814
9 At 28 100.0 4.322978 4.322978 18.68814
10 At 21 100.0 4.322978 4.322978 18.68814
11 At 7 100.0 4.322978 4.322978 18.68814
R² Ljung-Box p-value
0 -5.192297e+31 NaN
1 -5.192297e+31 NaN
2 -5.192297e+31 NaN
3 -5.192297e+31 NaN
4 NaN NaN
5 NaN NaN
6 NaN NaN
7 NaN NaN
8 NaN NaN
9 NaN NaN
10 NaN NaN
11 NaN NaN
Conclusion Garch sin rolling#
Al ajustar un modelo GARCH sin utilizar la técnica de ventana móvil, observé una mejora en la modelización de la serie temporal. Esto se tradujo en una mejor captura de la volatilidad y una reducción en los residuos en ciertas áreas. Sin embargo, a pesar de estos avances, el modelo aún no logró un ajuste óptimo que permitiera prever con precisión los movimientos del precio del bitcoin.
Cuando implementé el modelo GARCH con una ventana móvil, no obtuve resultados significativos en la identificación de patrones dentro de las variables analizadas, como precios, volatilidad y retornos acumulados del bitcoin. Este fenómeno puede atribuirse a dos factores principales. Primero, la selección de hiperparámetros para el modelo GARCH es un proceso que requiere considerable capacidad computacional. La limitación en los recursos disponibles pudo restringir la búsqueda exhaustiva de combinaciones óptimas para cada ventana móvil, lo que resultó en un ajuste inadecuado a las características específicas de los datos y afectó negativamente su precisión y capacidad predictiva.
En segundo lugar, los modelos GARCH pueden ser inadecuados para manejar datos que presentan alta volatilidad y comportamientos no lineales, como es el caso de los precios del bitcoin. Estos modelos asumen una estructura lineal en la varianza condicional, lo cual puede ser insuficiente para capturar la complejidad inherente a las series temporales caracterizadas por fluctuaciones abruptas y dinámicas no lineales.
En conclusión, aunque logré ciertas mejoras al aplicar un modelo GARCH sin rolling, la implementación de un enfoque con ventana móvil no permitió captar patrones significativos en los datos del bitcoin. Las limitaciones computacionales y la naturaleza volátil y no lineal de los precios son factores críticos que deben considerarse al seleccionar y ajustar modelos econométricos para este tipo de activos financieros.
Deep learning#
# import ipdb
def create_windows(ts, window_size, train_steps, validation_steps, test_steps):
#ipdb.set_trace()
segment_size_ = window_size + train_steps + validation_steps + test_steps
start = (len(ts)//segment_size_) * segment_size_
ts0 = ts[-start:]
Xtrains, ytrains, Xvalids, yvalids, Xtests, ytests = [], [], [], [], [], []
i = 0
while (len(ts0) - i + 1) >= segment_size_:
X, y = [], []
for _ in range(train_steps):
X.append(ts0[i:i + window_size])
y.append(ts0[i + window_size])
i += 1
Xtrains.append(X)
ytrains.append(y)
X, y = [], []
for _ in range(validation_steps):
X.append(ts0[i:i + window_size])
y.append(ts0[i + window_size])
i += 1
Xvalids.append(X)
yvalids.append(y)
X, y = [], []
for _ in range(test_steps):
X.append(ts0[i:i + window_size])
y.append(ts0[i + window_size])
i += 1
Xtests.append(X)
ytests.append(y)
return np.array(Xtrains), np.array(ytrains), np.array(Xvalids), np.array(yvalids), np.array(Xtests), np.array(ytests)
import matplotlib.pyplot as plt
import numpy as np
# Definir las combinaciones de tamaños de ventana y tamaños de paso de entrenamiento
window_sizes = [7, 14, 21, 28]
training_step_sizes = [7, 14, 21, 28]
validation_steps = 14
test_steps = 14
# Inicializar el gráfico
fig, ax = plt.subplots(figsize=(12, 8))
# Configurar parámetros de estilo para los pliegues
train_color = 'red'
val_color = 'blue'
test_color = 'green'
# Coordenadas iniciales
y_offset = 0
for window_size in window_sizes:
for train_steps in training_step_sizes:
# Coordenadas de inicio y final para los segmentos
train_start = 0
train_end = train_steps
val_start = train_end
val_end = val_start + validation_steps
test_start = val_end
test_end = test_start + test_steps
# Graficar pliegues de entrenamiento, validación y prueba
ax.plot(range(train_start, train_end), [y_offset] * (train_end - train_start), color=train_color, linewidth=2, label='Train' if y_offset == 0 else "")
ax.plot(range(val_start, val_end), [y_offset] * (val_end - val_start), color=val_color, linewidth=2, label='Validation' if y_offset == 0 else "")
ax.plot(range(test_start, test_end), [y_offset] * (test_end - test_start), color=test_color, linewidth=2, label='Test' if y_offset == 0 else "")
# Etiquetas con dimensiones
ax.text(train_start + (train_end - train_start) / 2, y_offset + 0.2, f"dim(X_train) = ({train_steps}, {window_size})", ha='center', color=train_color, fontsize=9)
ax.text(val_start + (val_end - val_start) / 2, y_offset + 0.2, f"dim(X_val) = ({validation_steps}, {window_size})", ha='center', color=val_color, fontsize=9)
ax.text(test_start + (test_end - test_start) / 2, y_offset + 0.2, f"dim(X_test) = ({test_steps}, {window_size})", ha='center', color=test_color, fontsize=9)
# Incrementar el offset vertical para el siguiente grupo
y_offset -= 1
# Configurar los ejes y leyenda
ax.set_xlabel('Time Index')
ax.set_yticks([])
ax.legend(loc='upper right')
ax.set_title("Visualización de Pliegues para Diferentes Tamaños de Ventana y Paso de Entrenamiento")
plt.show()
import numpy as np
import matplotlib.pyplot as plt
def visualize_windows(ts, window_size, train_steps, validation_steps, test_steps):
# Calcular el tamaño del segmento
segment_size_ = window_size + train_steps + validation_steps + test_steps
# Definir el punto de inicio para cortar la serie temporal
start = (len(ts) // segment_size_) * segment_size_
ts0 = ts[-start:]
fig, ax = plt.subplots(figsize=(12, 6))
# Graficar toda la serie temporal
ax.plot(range(len(ts)), ts, label='Serie Temporal Completa', color='lightgray')
# Indicar el conjunto de entrenamiento, validación y prueba
colors = {'train': 'blue', 'validation': 'orange', 'test': 'green'}
i = 0
count = 0
while (len(ts0) - i + 1) >= segment_size_:
# Ventanas de entrenamiento
for step in range(train_steps):
x_range = range(i, i + window_size)
ax.plot(x_range, ts0[i:i + window_size], color=colors['train'], label='Entrenamiento' if count == 0 else "")
i += 1
# Ventanas de validación
for step in range(validation_steps):
x_range = range(i, i + window_size)
ax.plot(x_range, ts0[i:i + window_size], color=colors['validation'], label='Validación' if count == 0 else "")
i += 1
# Ventanas de prueba
for step in range(test_steps):
x_range = range(i, i + window_size)
ax.plot(x_range, ts0[i:i + window_size], color=colors['test'], label='Prueba' if count == 0 else "")
i += 1
count += 1
# Añadir leyenda y título
ax.legend(loc='upper right')
ax.set_title(f'Visualización de ventanas deslizantes\nTamaño ventana: {window_size}, Pasos entrenamiento: {train_steps}, Validación: {validation_steps}, Prueba: {test_steps}')
plt.xlabel('Índice de Tiempo')
plt.ylabel('Valor de la Serie Temporal')
plt.show()
# Crear una serie temporal simulada
ts = np.sin(np.linspace(0, 50, 300)) # Serie temporal de ejemplo
# Parámetros para la visualización
window_size = 10
train_steps = 5
validation_steps = 3
test_steps = 2
# Llamada a la función de visualización
visualize_windows(ts, window_size, train_steps, validation_steps, test_steps)
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
def plot_windowing_structure(window_size, train_steps, validation_steps, test_steps, num_rows=10):
fig, ax = plt.subplots(figsize=(12, 6))
# Crear una serie temporal de ejemplo
series_len = num_rows * (train_steps + validation_steps + test_steps)
ts = np.arange(1, series_len + 1)
# Definir colores
train_color = 'red'
val_color = 'blue'
test_color = 'green'
for row in range(num_rows):
# Calcular la posición base de inicio para la fila
start_pos = row * (train_steps + validation_steps + test_steps)
# Entrenamiento
for i in range(train_steps):
x_start = start_pos + i
ax.add_patch(patches.Rectangle((x_start, row), window_size, 1, edgecolor='none', facecolor=train_color))
# Validación
for i in range(train_steps, train_steps + validation_steps):
x_start = start_pos + i
ax.add_patch(patches.Rectangle((x_start, row), window_size, 1, edgecolor='none', facecolor=val_color))
# Prueba
for i in range(train_steps + validation_steps, train_steps + validation_steps + test_steps):
x_start = start_pos + i
ax.add_patch(patches.Rectangle((x_start, row), window_size, 1, edgecolor='none', facecolor=test_color))
# Etiquetas y leyenda
ax.set_xlim(0, series_len)
ax.set_ylim(-1, num_rows)
ax.set_xlabel('Índice de tiempo')
ax.set_ylabel('Iteraciones (filas)')
# Crear leyenda personalizada
legend_handles = [
patches.Patch(color=train_color, label='Entrenamiento'),
patches.Patch(color=val_color, label='Validación'),
patches.Patch(color=test_color, label='Prueba')
]
ax.legend(handles=legend_handles)
plt.title(f'Estructura de ventanas deslizantes: {window_size} pasos por ventana')
plt.grid(False)
plt.show()
# Parámetros de ventana
window_size = 7
train_steps = 14
validation_steps = 7
test_steps = 7
# Llamar a la función para graficar la estructura de las ventanas
plot_windowing_structure(window_size, train_steps, validation_steps, test_steps, num_rows=5)
import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.preprocessing import MinMaxScaler
from torch.utils.data import DataLoader, TensorDataset
from sklearn.metrics import r2_score
import itertools
import os
import yfinance as yf
import pandas as pd
import warnings
warnings.filterwarnings("ignore")
import numpy as np
from matplotlib import pyplot as plt
import seaborn as sns
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from sklearn.metrics import mean_absolute_percentage_error, mean_squared_error, r2_score
from scipy.stats import kurtosis, skew, jarque_bera
from statsmodels.tsa.stattools import adfuller, kpss
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm
from scipy.stats import kurtosistest, skewtest, jarque_bera
from statsmodels.tsa.arima.model import ARIMA
# Function to calculate MAPE, RMSE, and R² adj
def calculate_metrics(y_true, y_pred, n, p):
y_true = y_true.flatten()
y_pred = y_pred.flatten()
mape = np.mean(np.abs((y_true - y_pred) / y_true)) * 100
rmse = np.sqrt(np.mean((y_true - y_pred) ** 2))
ss_res = np.sum((y_true - y_pred) ** 2)
ss_tot = np.sum((y_true - np.mean(y_true)) ** 2)
r2 = 1 - (ss_res / ss_tot)
r2_adj = 1 - ((1 - r2) * (n - 1) / (n - p - 1))
return mape, rmse, r2, r2_adj
# Function to evaluate the model and create a dataframe row
def evaluate_and_create_row(model, data_loader, criterion, scaler, window_size, model_name):
model.eval()
all_predictions = []
all_targets = []
with torch.no_grad():
for X_batch, y_batch in data_loader:
outputs = model(X_batch)
all_predictions.append(outputs)
all_targets.append(y_batch)
# Concatenate all predictions and targets
all_predictions = torch.cat(all_predictions, dim=0).cpu().numpy()
all_targets = torch.cat(all_targets, dim=0).cpu().numpy()
all_predictions = scaler.inverse_transform(all_predictions.reshape(-1, 1))
all_targets = scaler.inverse_transform(all_targets.reshape(-1, 1))
n = len(all_targets)
p = window_size
mape, rmse, r2, r2_adj = calculate_metrics(np.concatenate(all_targets), np.concatenate(all_predictions), n, p)
metrics_df = pd.DataFrame({
'Model': [model_name],
'MAPE': [mape],
'RMSE': [rmse],
'R²': [r2],
'R² adj': [r2_adj]
})
return all_predictions, all_targets, metrics_df
class LSTMModel(nn.Module):
def __init__(self, input_size, hidden_size, output_size, num_layers):
super(LSTMModel, self).__init__()
self.num_layers = num_layers
self.hidden_size = hidden_size
self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
h_0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
c_0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
out, _ = self.lstm(x, (h_0, c_0))
out = self.fc(out) # Apply fully connected layer to each time step
return out
import pandas as pd
import numpy as np
from statsmodels.graphics.tsaplots import plot_acf
import matplotlib.pyplot as plt
from statsmodels.api import qqplot
from statsmodels.tsa.stattools import acf
from statsmodels.stats.diagnostic import normal_ad
from scipy import stats
# Normalize the data
# Ruta del archivo CSV
BHdata = "C:\\Users\\Maestriadatos\\Documents\\lizcanollerenaparcial\\docs\\bitcoinhistoricaldata.csv"
df = pd.read_csv(BHdata)
# Convertir la columna 'Price', 'Open', 'High', 'Low' eliminando comas y convirtiendo a float
df['Price'] = df['Price'].str.replace(',', '').astype(float)
df['Open'] = df['Open'].str.replace(',', '').astype(float)
df['High'] = df['High'].str.replace(',', '').astype(float)
df['Low'] = df['Low'].str.replace(',', '').astype(float)
# Convertir la columna 'Vol.' eliminando comas, reemplazando 'K' por 'e3' (miles) y 'M' por 'e6' (millones), luego convertir a numérico
df['Vol.'] = df['Vol.'].str.replace('K', 'e3').str.replace('M', 'e6').str.replace(',', '')
df['Vol.'] = pd.to_numeric(df['Vol.'], errors='coerce')
# Convertir la columna 'Change %' eliminando el símbolo '%' y convirtiendo a float
df['Change %'] = df['Change %'].str.replace('%', '').astype(float)
# Cargar los datos
scaler = MinMaxScaler(feature_range=(0, 1))
btc_close_scaled = scaler.fit_transform(df.Price.values.reshape(-1, 1))
# Convert to tensor
btc_close_tensor = torch.FloatTensor(btc_close_scaled).view(-1)
shared_folder_path = "C:\\Users\\Maestriadatos\\Documents\\lizcanollerenaparcial\\docs"
files = os.listdir(shared_folder_path)
print(files)
['.git', 'bitcoinhistoricaldata.csv', 'intro.md', 'logo.png', 'lstm_modelAt_14_14.pth', 'lstm_modelAt_14_21.pth', 'lstm_modelAt_14_28.pth', 'lstm_modelAt_14_7.pth', 'lstm_modelAt_21_14.pth', 'lstm_modelAt_21_21.pth', 'lstm_modelAt_21_28.pth', 'lstm_modelAt_21_7.pth', 'lstm_modelAt_28_14.pth', 'lstm_modelAt_28_21.pth', 'lstm_modelAt_28_28.pth', 'lstm_modelAt_28_7.pth', 'lstm_modelAt_7_14.pth', 'lstm_modelAt_7_21.pth', 'lstm_modelAt_7_28.pth', 'lstm_modelAt_7_7.pth', 'lstm_modelVol7_14_14.pth', 'lstm_modelVol7_14_21.pth', 'lstm_modelVol7_14_28.pth', 'lstm_modelVol7_14_7.pth', 'lstm_modelVol7_21_14.pth', 'lstm_modelVol7_21_21.pth', 'lstm_modelVol7_21_28.pth', 'lstm_modelVol7_21_7.pth', 'lstm_modelVol7_28_14.pth', 'lstm_modelVol7_28_21.pth', 'lstm_modelVol7_28_28.pth', 'lstm_modelVol7_28_7.pth', 'lstm_modelVol7_7_14.pth', 'lstm_modelVol7_7_21.pth', 'lstm_modelVol7_7_28.pth', 'lstm_modelVol7_7_7.pth', 'lstm_model_14_14.pth', 'lstm_model_14_21.pth', 'lstm_model_14_28.pth', 'lstm_model_14_7.pth', 'lstm_model_21_14.pth', 'lstm_model_21_21.pth', 'lstm_model_21_28.pth', 'lstm_model_21_7.pth', 'lstm_model_28_14.pth', 'lstm_model_28_21.pth', 'lstm_model_28_28.pth', 'lstm_model_28_7.pth', 'lstm_model_7_14.pth', 'lstm_model_7_21.pth', 'lstm_model_7_28.pth', 'lstm_model_7_7.pth', 'markdown-notebooks.md', 'markdown.md', 'mlp_model_14_14.pth', 'mlp_model_14_21.pth', 'mlp_model_14_28.pth', 'mlp_model_14_7.pth', 'mlp_model_21_14.pth', 'mlp_model_21_21.pth', 'mlp_model_21_28.pth', 'mlp_model_21_7.pth', 'mlp_model_28_14.pth', 'mlp_model_28_21.pth', 'mlp_model_28_28.pth', 'mlp_model_28_7.pth', 'mlp_model_7_14.pth', 'mlp_model_7_21.pth', 'mlp_model_7_28.pth', 'mlp_model_7_7.pth', 'notebooks.ipynb', 'references.bib', 'requirements.txt', 'rnn_modelAt_14_14.pth', 'rnn_modelAt_14_21.pth', 'rnn_modelAt_14_28.pth', 'rnn_modelAt_14_7.pth', 'rnn_modelAt_21_14.pth', 'rnn_modelAt_21_21.pth', 'rnn_modelAt_21_28.pth', 'rnn_modelAt_21_7.pth', 'rnn_modelAt_28_14.pth', 'rnn_modelAt_28_21.pth', 'rnn_modelAt_28_28.pth', 'rnn_modelAt_28_7.pth', 'rnn_modelAt_7_14.pth', 'rnn_modelAt_7_21.pth', 'rnn_modelAt_7_28.pth', 'rnn_modelAt_7_7.pth', 'rnn_modelVol_14_14.pth', 'rnn_modelVol_14_21.pth', 'rnn_modelVol_14_28.pth', 'rnn_modelVol_14_7.pth', 'rnn_modelVol_21_14.pth', 'rnn_modelVol_21_21.pth', 'rnn_modelVol_21_28.pth', 'rnn_modelVol_21_7.pth', 'rnn_modelVol_28_14.pth', 'rnn_modelVol_28_21.pth', 'rnn_modelVol_28_28.pth', 'rnn_modelVol_28_7.pth', 'rnn_modelVol_7_14.pth', 'rnn_modelVol_7_21.pth', 'rnn_modelVol_7_28.pth', 'rnn_modelVol_7_7.pth', 'rnn_model_14_14.pth', 'rnn_model_14_21.pth', 'rnn_model_14_28.pth', 'rnn_model_14_7.pth', 'rnn_model_21_14.pth', 'rnn_model_21_21.pth', 'rnn_model_21_28.pth', 'rnn_model_21_7.pth', 'rnn_model_28_14.pth', 'rnn_model_28_21.pth', 'rnn_model_28_28.pth', 'rnn_model_28_7.pth', 'rnn_model_7_14.pth', 'rnn_model_7_21.pth', 'rnn_model_7_28.pth', 'rnn_model_7_7.pth', '_build', '_config.yml', '_toc.yml']
window_sizes = [7, 14, 21, 28]
training_step_sizes = [7, 14, 21, 28]
columns = ['Model', 'MAPE', 'RMSE', 'R²', 'R² adj']
lstm_result_df = pd.DataFrame(columns=columns)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'Device: {device}')
for window_size, train_steps in itertools.product(window_sizes, training_step_sizes):
# Create windows
print(f'Window Size: {window_size}, Training Step Size: {train_steps}')
model_name = f'lstm_model_{window_size}_{train_steps}.pth'
if os.path.exists(os.path.join(shared_folder_path, model_name)):
checkpoint = torch.load(os.path.join(shared_folder_path, model_name))
valid_metrics_df = checkpoint['valid_metrics_df']
lstm_result_df = pd.concat([lstm_result_df, valid_metrics_df], ignore_index=True)
continue
validation_steps = 14
test_steps = 14
# Create windows
Xtrains, ytrains, Xvalids, yvalids, Xtests, ytests = create_windows(btc_close_tensor, window_size, train_steps, validation_steps, test_steps)
# Reshape the data to match the required input shape
Xtrains = torch.tensor(Xtrains, dtype=torch.float32).view(-1, train_steps, window_size).to(device)
ytrains = torch.tensor(ytrains, dtype=torch.float32).view(-1, train_steps, 1).to(device)
Xvalids = torch.tensor(Xvalids, dtype=torch.float32).view(-1, validation_steps, window_size).to(device)
yvalids = torch.tensor(yvalids, dtype=torch.float32).view(-1, validation_steps, 1).to(device)
Xtests = torch.tensor(Xtests, dtype=torch.float32).view(-1, test_steps, window_size).to(device)
ytests = torch.tensor(ytests, dtype=torch.float32).view(-1, test_steps, 1).to(device)
train_dataset = TensorDataset(Xtrains, ytrains)
valid_dataset = TensorDataset(Xvalids, yvalids)
test_dataset = TensorDataset(Xtests, ytests)
# batch_size = Xtrains.shape[0] # Ensure batch_size matches the first dimension of Xtrains
batch_size = 1
train_loader_original = DataLoader(train_dataset, batch_size=batch_size, shuffle=False)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
valid_loader = DataLoader(valid_dataset, batch_size=batch_size, shuffle=False)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
# Model Parameters
input_size = window_size
hidden_size = 25
output_size = 1
num_layers = 6
model = LSTMModel(input_size, hidden_size, output_size, num_layers).to(device)
# Training parameters
num_epochs = 100
learning_rate = 0.001
# Loss and optimizer
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
# Training loop
for epoch in range(num_epochs):
model.train()
epoch_loss = 0
for X_batch, y_batch in train_loader:
optimizer.zero_grad() # Zero out the gradients for the next batch
outputs = model(X_batch)
loss = criterion(outputs, y_batch)
loss.backward() # Backpropagation
optimizer.step() # Optimize the weights
epoch_loss += loss.item()
if (epoch + 1) % 10 == 0:
avg_loss = epoch_loss / len(train_loader)
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {avg_loss:.4f}')
_, _, valid_metrics_df = evaluate_and_create_row(model, valid_loader, criterion, scaler, window_size, model_name)
lstm_result_df = pd.concat([lstm_result_df, valid_metrics_df], ignore_index=True)
torch.save({
'input_size': input_size,
'hidden_size': hidden_size,
'output_size': output_size,
'num_layers': num_layers,
'state_dict': model.state_dict(),
'test_loader': test_loader,
'valid_metrics_df': valid_metrics_df
}, os.path.join(shared_folder_path, model_name))
print("Model saved successfully!")
Device: cpu
Window Size: 7, Training Step Size: 7
Window Size: 7, Training Step Size: 14
Window Size: 7, Training Step Size: 21
Window Size: 7, Training Step Size: 28
Window Size: 14, Training Step Size: 7
Window Size: 14, Training Step Size: 14
Window Size: 14, Training Step Size: 21
Window Size: 14, Training Step Size: 28
Window Size: 21, Training Step Size: 7
Window Size: 21, Training Step Size: 14
Window Size: 21, Training Step Size: 21
Window Size: 21, Training Step Size: 28
Window Size: 28, Training Step Size: 7
Window Size: 28, Training Step Size: 14
Window Size: 28, Training Step Size: 21
Window Size: 28, Training Step Size: 28
# Model Parameters
input_size = window_size
hidden_size = 25
output_size = 1
num_layers = 6
model_with_min_r2_adj = lstm_result_df.loc[lstm_result_df['R² adj'].idxmax(), 'Model']
print(f"Model with the smallest R² adjusted: {model_with_min_r2_adj}")
model = LSTMModel(input_size, hidden_size, output_size, num_layers).to(device)
checkpoint = torch.load(os.path.join(shared_folder_path, model_with_min_r2_adj))
input_size = checkpoint['input_size']
hidden_size = checkpoint['hidden_size']
output_size = checkpoint['output_size']
num_layers = checkpoint['num_layers']
test_loader = checkpoint['test_loader']
lstm_model = LSTMModel(input_size, hidden_size, output_size, num_layers)
lstm_model.load_state_dict(checkpoint['state_dict'])
lstm_model.to(device)
print("Model loaded successfully!")
Model with the smallest R² adjusted: lstm_model_14_21.pth
Model loaded successfully!
lstm_result_df
| Model | MAPE | RMSE | R² | R² adj | |
|---|---|---|---|---|---|
| 0 | lstm_model_7_7.pth | 13823.792969 | 3889.401367 | 0.939962 | 0.939749 |
| 1 | lstm_model_7_14.pth | 8316.774414 | 1277.516479 | 0.993282 | 0.993254 |
| 2 | lstm_model_7_21.pth | 9519.721680 | 2633.489014 | 0.970614 | 0.970467 |
| 3 | lstm_model_7_28.pth | 8158.646484 | 1525.957520 | 0.989754 | 0.989695 |
| 4 | lstm_model_14_7.pth | 9025.233398 | 2417.115479 | 0.975942 | 0.975771 |
| 5 | lstm_model_14_14.pth | 3142.239990 | 954.562744 | 0.996184 | 0.996151 |
| 6 | lstm_model_14_21.pth | 2346.378418 | 911.253235 | 0.996390 | 0.996354 |
| 7 | lstm_model_14_28.pth | 8347.414062 | 1983.402710 | 0.982615 | 0.982415 |
| 8 | lstm_model_21_7.pth | 10256.427734 | 2825.454102 | 0.966234 | 0.965871 |
| 9 | lstm_model_21_14.pth | 23891.451172 | 1722.454346 | 0.987155 | 0.986990 |
| 10 | lstm_model_21_21.pth | 20530.283203 | 1048.479858 | 0.995303 | 0.995233 |
| 11 | lstm_model_21_28.pth | 6618.501953 | 1443.454102 | 0.990350 | 0.990180 |
| 12 | lstm_model_28_7.pth | 35114.261719 | 2558.657227 | 0.971567 | 0.971158 |
| 13 | lstm_model_28_14.pth | 13888.269531 | 2208.796875 | 0.978633 | 0.978261 |
| 14 | lstm_model_28_21.pth | 18652.074219 | 1173.737305 | 0.993867 | 0.993742 |
| 15 | lstm_model_28_28.pth | 23398.820312 | 1144.378540 | 0.994465 | 0.994336 |
En los resultados obtenidos con los modelos LSTM para la predicción del precio de Bitcoin, se puede notar que el rendimiento varía significativamente en función del tamaño de la ventana y el horizonte de predicción. En general, los modelos con una ventana de 14 días tienden a ofrecer las mejores métricas, como el caso del modelo con ventana y horizonte de 14 días (lstm_model_14_14.pth) que obtuvo el MAPE más bajo (3142.24) y un R² cercano a 1 (0.996184), lo que indica una alta precisión en sus predicciones.
Los modelos con ventanas de 7 y 21 días también muestran buenos resultados, pero hay más variabilidad en el error. Por ejemplo, el modelo con ventana de 7 días y horizonte de 14 (lstm_model_7_14.pth) presenta un error menor (MAPE de 8316.77) en comparación con el mismo modelo con horizonte de 21 días (lstm_model_7_21.pth), que tiene un MAPE mayor (9519.72).
Finalmente, los modelos con ventanas de 28 días tienen más dificultad para mantener una precisión constante, especialmente en los horizontes más cortos, como se observa en el modelo lstm_model_28_7.pth, que tiene un MAPE muy elevado (35114.26), lo que indica una menor capacidad para capturar las variaciones del precio en esos intervalos.
# Loss and optimizer
criterion = nn.MSELoss()
def count_parameters(model):
return sum(p.numel() for p in model.parameters() if p.requires_grad)
def calculate_adjusted_r2(all_targets, all_predictions, model):
n = len(all_targets)
k = count_parameters(model) # Total number of trainable parameters
r2 = r2_score(all_targets, all_predictions)
r2_adj = 1 - (1 - r2) * (n - 1) / (n - k - 1)
print(f"R²: {r2:.4f}")
print(f"R² adjusted: {r2_adj:.4f}")
print(f"Number of trainable parameters: {k}")
print(f"Nunmber of measures : {n}")
return r2_adj
def evaluate_model_residues(model, data_loader, criterion):
model.eval()
all_predictions = []
all_targets = []
with torch.no_grad():
for X_batch, y_batch in data_loader:
outputs = model(X_batch)
all_predictions.append(outputs)
all_targets.append(y_batch)
# Concatenate all predictions and targets
all_predictions = torch.cat(all_predictions, dim=0).cpu().numpy().reshape(-1)
all_targets = torch.cat(all_targets, dim=0).cpu().numpy().reshape(-1)
residues = all_targets - all_predictions
# Calculate statistics
mape = mean_absolute_percentage_error(all_targets, all_predictions)
rmse = np.sqrt(mean_squared_error(all_targets, all_predictions))
r2 = r2_score(all_targets, all_predictions)
kurt_stat, kurt_p_value = kurtosistest(residues)
skew_stat, skew_p_value = skewtest(residues)
jb_stat, jb_p_value = jarque_bera(residues)
adf_stat, adf_p_value, _, _, _, _ = adfuller(residues)
kpss_stat, kpss_p_value, _, _ = kpss(residues)
results = {
'MAPE': mape,
'RMSE': rmse,
# 'R2 adj': r2_adj,
'R2': r2,
'Kurtosis (statistic)': kurt_stat,
'Kurtosis (p-value)': kurt_p_value,
'Skewness (statistic)': skew_stat,
'Skewness (p-value)': skew_p_value,
'Jarque-Bera (statistic)': jb_stat,
'Jarque-Bera (p-value)': jb_p_value,
'Dickey Fuller (statistic)': adf_stat,
'Dickey Fuller (p-value)': adf_p_value,
'KPSS test (statistic)': kpss_stat,
'KPSS test (p-value)': kpss_p_value
}
results_df = pd.DataFrame.from_dict(results, orient='index', columns=['Value'])
# Plotting the time series of residues
plt.figure(figsize=(10, 6))
plt.plot(residues)
plt.title('Time Series of Residues')
plt.xlabel('Time')
plt.ylabel('Residues')
plt.show()
# Plotting the histogram of residues
plt.figure(figsize=(10, 6))
sns.histplot(residues, kde=True)
plt.title('Histogram of Residues')
plt.xlabel('Residues')
plt.ylabel('Frequency')
plt.show()
# Q-Q plot of residues
plt.figure(figsize=(10, 6))
sm.qqplot(residues, line='s')
plt.title('Q-Q Plot of Residues')
plt.show()
# ACF plot of residues
plt.figure(figsize=(10, 6))
sm.graphics.tsa.plot_acf(residues, lags=40)
plt.title('Autocorrelation Function (ACF) of Residues')
plt.show()
return residues, results_df
residues, results_df = evaluate_model_residues(lstm_model, test_loader, criterion)
results_df
<Figure size 1000x600 with 0 Axes>
<Figure size 1000x600 with 0 Axes>
| Value | |
|---|---|
| MAPE | 9.413034e+10 |
| RMSE | 1.179102e-02 |
| R2 | 9.968290e-01 |
| Kurtosis (statistic) | 1.727034e+01 |
| Kurtosis (p-value) | 7.867481e-67 |
| Skewness (statistic) | -1.122920e+01 |
| Skewness (p-value) | 2.931350e-29 |
| Jarque-Bera (statistic) | 1.030804e+04 |
| Jarque-Bera (p-value) | 0.000000e+00 |
| Dickey Fuller (statistic) | -8.623177e+00 |
| Dickey Fuller (p-value) | 6.027746e-14 |
| KPSS test (statistic) | 3.385352e-01 |
| KPSS test (p-value) | 1.000000e-01 |
Analisis del las graficas#
Al analizar los residuos de la red neuronal LSTM aplicada a la serie de tiempo de precios, comienzo observando el gráfico de los residuos en función del tiempo. Se puede notar que los residuos presentan una alta concentración al inicio, lo que podría indicar que el modelo tiene dificultades en ajustar la serie en las primeras etapas. Sin embargo, a medida que avanza el tiempo, esta concentración disminuye, lo que sugiere una mejoría en el ajuste del modelo en los periodos posteriores.
El histograma de los residuos muestra una distribución que, a simple vista, parece normal, lo cual en principio es un indicador favorable para la capacidad predictiva del modelo. No obstante, se observa una columna central significativamente alta, lo que indica una concentración de valores en torno al cero. Esto podría señalar que el modelo subestima la variabilidad real de la serie de precios o que existen ciertos patrones persistentes en los residuos no capturados por la LSTM.
El QQ plot, que normalmente ayuda a verificar si los residuos siguen una distribución normal, revela una disposición sinusoidal de los puntos alrededor de la línea de referencia central. Este patrón indica la presencia de colas más pesadas o algún tipo de asimetría en los residuos, desviándose de la normalidad. La forma sinusoidal sugiere la presencia de patrones cíclicos o dependencias no capturadas adecuadamente por el modelo.
Por último, la prueba ACF muestra tres rezagos fuera de la banda de significancia, lo cual sugiere una autocorrelación residual en dichos puntos. Esto es indicativo de que el modelo no ha logrado eliminar por completo la dependencia temporal en los datos, lo que podría implicar la necesidad de ajustar hiperparámetros, aumentar la complejidad del modelo o probar alguna transformación adicional en los datos.
En conjunto, estos gráficos sugieren que, si bien el modelo captura algunos patrones en la serie de precios, aún existen dependencias y patrones no ajustados completamente. Estos hallazgos me llevan a considerar ajustes adicionales en la arquitectura del modelo o en los preprocesamientos aplicados a la serie para mejorar la capacidad predictiva y reducir la dependencia residual.
Analisis de las pruebas#
Al analizar los residuos de la predicción de mi modelo LSTM para el precio del Bitcoin, encuentro resultados mixtos. El 𝑅 2 R 2 de 0.9968 indica un ajuste sólido, y el RMSE bajo de 0.0118 muestra que los errores absolutos son pequeños en términos relativos. Sin embargo, el MAPE extremadamente alto ( 9.41 × 1 0 10 9.41×10 10 ) sugiere que el modelo presenta problemas al generalizar, especialmente en valores extremos. La distribución de los residuos muestra una kurtosis elevada (17.27) y una asimetría negativa fuerte (-11.23), lo que refleja colas pesadas y sesgo negativo, indicando que el modelo subestima algunos valores. La prueba de Jarque-Bera confirma la falta de normalidad, lo cual sugiere errores no aleatorios que pueden afectar la robustez del modelo frente a valores atípicos. Afortunadamente, las pruebas de estacionariedad, tanto Dickey-Fuller como KPSS, indican que los residuos son estacionarios, lo cual es positivo para la estabilidad del modelo en el tiempo.
test_predictions, test_targets, test_metrics_df = evaluate_and_create_row(lstm_model, test_loader, criterion, scaler, window_size, "LSTM")
plt.plot(test_predictions.reshape(-1), label='test_predictions')
plt.plot(test_targets.reshape(-1), label='test_targets')
plt.legend()
plt.show()
test_metrics_df
| Model | MAPE | RMSE | R² | R² adj | |
|---|---|---|---|---|---|
| 0 | LSTM | 2427.556885 | 861.525208 | 0.996829 | 0.996765 |
Al observar el gráfico de predicción en el conjunto de validación, se puede ver que el modelo LSTM logra un ajuste notablemente preciso. Las predicciones siguen de cerca el comportamiento de los datos reales, lo que indica que el modelo ha capturado adecuadamente los patrones principales de la serie de precios en el periodo de validación. Esta precisión en el ajuste es un buen indicador de que el modelo es capaz de generalizar, al menos en los datos de validación, lo que respalda su utilidad en aplicaciones futuras.
Sin embargo, los análisis previos de los residuos sugieren que aún quedan áreas por mejorar. Aunque la predicción en el conjunto de validación es prometedora, la persistencia de patrones cíclicos y autocorrelaciones en los residuos, junto con la fuerte concentración de valores en torno al cero, señala posibles ajustes necesarios para mejorar la robustez del modelo y su capacidad para capturar variaciones menores o patrones complejos en el tiempo.
import matplotlib.pyplot as plt
import pandas as pd
# Asegúrate de que 'lstm_result_df' contiene resultados de cada combinación de `window_size` y `train_steps`
# Añadimos una columna para numerar las corridas de entrenamiento
lstm_result_df['Run'] = range(1, len(lstm_result_df) + 1)
# Selecciona la métrica de los residuos que deseas graficar en el eje Y
# Aquí usaremos 'RMSE' como ejemplo; ajusta si prefieres otra métrica, como 'MAPE'
metric_to_plot = 'RMSE'
plt.figure(figsize=(10, 6))
plt.plot(lstm_result_df['Run'], lstm_result_df[metric_to_plot], marker='o', linestyle='-')
plt.xlabel("Run (Combination of Window Size and Training Step Size)")
plt.ylabel(f"{metric_to_plot} (Residual Score)")
plt.title(f"Error/Score vs Run for LSTM Model")
plt.grid()
plt.show()
LSTM Retornos acumulados#
# Eliminar las filas con valores nulos en la columna 'Price'
df.dropna(subset=['Price'], inplace=True)
# Calcular el retorno diario Rt
df['Rt'] = (df['Price'] - df['Price'].shift(1)) / df['Price'].shift(1)
# Calcular el retorno acumulado diario At
df['At'] = df['Rt'].cumsum()
# Función para calcular la volatilidad usando ventanas móviles
def calcular_volatilidad(data, ventana):
"""
Esta función calcula la volatilidad (desviación estándar)
de los retornos diarios utilizando una ventana móvil.
"""
return data['Rt'].rolling(window=ventana).std()
# Calcular volatilidad para diferentes ventanas
ventanas = [7, 14, 21, 28]
for ventana in ventanas:
df[f'Volatilidad_{ventana}'] = calcular_volatilidad(df, ventana)
# Cargar los datos
scaler = MinMaxScaler(feature_range=(0, 1))
btc_At_scaled = scaler.fit_transform(df.At.values.reshape(-1, 1))
# Convert to tensor
btc_At_tensor = torch.FloatTensor(btc_At_scaled).view(-1)
shared_folder_path = "C:\\Users\\Maestriadatos\\Documents\\lizcanollerenaparcial\\docs"
files = os.listdir(shared_folder_path)
print(files)
['.git', 'bitcoinhistoricaldata.csv', 'intro.md', 'logo.png', 'lstm_modelAt_14_14.pth', 'lstm_modelAt_14_21.pth', 'lstm_modelAt_14_28.pth', 'lstm_modelAt_14_7.pth', 'lstm_modelAt_21_14.pth', 'lstm_modelAt_21_21.pth', 'lstm_modelAt_21_28.pth', 'lstm_modelAt_21_7.pth', 'lstm_modelAt_28_14.pth', 'lstm_modelAt_28_21.pth', 'lstm_modelAt_28_28.pth', 'lstm_modelAt_28_7.pth', 'lstm_modelAt_7_14.pth', 'lstm_modelAt_7_21.pth', 'lstm_modelAt_7_28.pth', 'lstm_modelAt_7_7.pth', 'lstm_modelVol7_14_14.pth', 'lstm_modelVol7_14_21.pth', 'lstm_modelVol7_14_28.pth', 'lstm_modelVol7_14_7.pth', 'lstm_modelVol7_21_14.pth', 'lstm_modelVol7_21_21.pth', 'lstm_modelVol7_21_28.pth', 'lstm_modelVol7_21_7.pth', 'lstm_modelVol7_28_14.pth', 'lstm_modelVol7_28_21.pth', 'lstm_modelVol7_28_28.pth', 'lstm_modelVol7_28_7.pth', 'lstm_modelVol7_7_14.pth', 'lstm_modelVol7_7_21.pth', 'lstm_modelVol7_7_28.pth', 'lstm_modelVol7_7_7.pth', 'lstm_model_14_14.pth', 'lstm_model_14_21.pth', 'lstm_model_14_28.pth', 'lstm_model_14_7.pth', 'lstm_model_21_14.pth', 'lstm_model_21_21.pth', 'lstm_model_21_28.pth', 'lstm_model_21_7.pth', 'lstm_model_28_14.pth', 'lstm_model_28_21.pth', 'lstm_model_28_28.pth', 'lstm_model_28_7.pth', 'lstm_model_7_14.pth', 'lstm_model_7_21.pth', 'lstm_model_7_28.pth', 'lstm_model_7_7.pth', 'markdown-notebooks.md', 'markdown.md', 'mlp_model_14_14.pth', 'mlp_model_14_21.pth', 'mlp_model_14_28.pth', 'mlp_model_14_7.pth', 'mlp_model_21_14.pth', 'mlp_model_21_21.pth', 'mlp_model_21_28.pth', 'mlp_model_21_7.pth', 'mlp_model_28_14.pth', 'mlp_model_28_21.pth', 'mlp_model_28_28.pth', 'mlp_model_28_7.pth', 'mlp_model_7_14.pth', 'mlp_model_7_21.pth', 'mlp_model_7_28.pth', 'mlp_model_7_7.pth', 'notebooks.ipynb', 'references.bib', 'requirements.txt', 'rnn_modelAt_14_14.pth', 'rnn_modelAt_14_21.pth', 'rnn_modelAt_14_28.pth', 'rnn_modelAt_14_7.pth', 'rnn_modelAt_21_14.pth', 'rnn_modelAt_21_21.pth', 'rnn_modelAt_21_28.pth', 'rnn_modelAt_21_7.pth', 'rnn_modelAt_28_14.pth', 'rnn_modelAt_28_21.pth', 'rnn_modelAt_28_28.pth', 'rnn_modelAt_28_7.pth', 'rnn_modelAt_7_14.pth', 'rnn_modelAt_7_21.pth', 'rnn_modelAt_7_28.pth', 'rnn_modelAt_7_7.pth', 'rnn_modelVol_14_14.pth', 'rnn_modelVol_14_21.pth', 'rnn_modelVol_14_28.pth', 'rnn_modelVol_14_7.pth', 'rnn_modelVol_21_14.pth', 'rnn_modelVol_21_21.pth', 'rnn_modelVol_21_28.pth', 'rnn_modelVol_21_7.pth', 'rnn_modelVol_28_14.pth', 'rnn_modelVol_28_21.pth', 'rnn_modelVol_28_28.pth', 'rnn_modelVol_28_7.pth', 'rnn_modelVol_7_14.pth', 'rnn_modelVol_7_21.pth', 'rnn_modelVol_7_28.pth', 'rnn_modelVol_7_7.pth', 'rnn_model_14_14.pth', 'rnn_model_14_21.pth', 'rnn_model_14_28.pth', 'rnn_model_14_7.pth', 'rnn_model_21_14.pth', 'rnn_model_21_21.pth', 'rnn_model_21_28.pth', 'rnn_model_21_7.pth', 'rnn_model_28_14.pth', 'rnn_model_28_21.pth', 'rnn_model_28_28.pth', 'rnn_model_28_7.pth', 'rnn_model_7_14.pth', 'rnn_model_7_21.pth', 'rnn_model_7_28.pth', 'rnn_model_7_7.pth', '_build', '_config.yml', '_toc.yml']
window_sizes = [7, 14, 21, 28]
training_step_sizes = [7, 14, 21, 28]
columns = ['Model', 'MAPE', 'RMSE', 'R²', 'R² adj']
lstm_result_df = pd.DataFrame(columns=columns)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'Device: {device}')
for window_size, train_steps in itertools.product(window_sizes, training_step_sizes):
# Create windows
print(f'Window Size: {window_size}, Training Step Size: {train_steps}')
model_name = f'lstm_modelAt_{window_size}_{train_steps}.pth'
if os.path.exists(os.path.join(shared_folder_path, model_name)):
checkpoint = torch.load(os.path.join(shared_folder_path, model_name))
valid_metrics_df = checkpoint['valid_metrics_df']
lstm_result_df = pd.concat([lstm_result_df, valid_metrics_df], ignore_index=True)
continue
validation_steps = 14
test_steps = 14
# Create windows
Xtrains, ytrains, Xvalids, yvalids, Xtests, ytests = create_windows(btc_At_tensor, window_size, train_steps, validation_steps, test_steps)
# Reshape the data to match the required input shape
Xtrains = torch.tensor(Xtrains, dtype=torch.float32).view(-1, train_steps, window_size).to(device)
ytrains = torch.tensor(ytrains, dtype=torch.float32).view(-1, train_steps, 1).to(device)
Xvalids = torch.tensor(Xvalids, dtype=torch.float32).view(-1, validation_steps, window_size).to(device)
yvalids = torch.tensor(yvalids, dtype=torch.float32).view(-1, validation_steps, 1).to(device)
Xtests = torch.tensor(Xtests, dtype=torch.float32).view(-1, test_steps, window_size).to(device)
ytests = torch.tensor(ytests, dtype=torch.float32).view(-1, test_steps, 1).to(device)
train_dataset = TensorDataset(Xtrains, ytrains)
valid_dataset = TensorDataset(Xvalids, yvalids)
test_dataset = TensorDataset(Xtests, ytests)
# batch_size = Xtrains.shape[0] # Ensure batch_size matches the first dimension of Xtrains
batch_size = 16
train_loader_original = DataLoader(train_dataset, batch_size=batch_size, shuffle=False)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
valid_loader = DataLoader(valid_dataset, batch_size=batch_size, shuffle=False)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
# Model Parameters
input_size = window_size
hidden_size = 5
output_size = 1
num_layers = 6
model = LSTMModel(input_size, hidden_size, output_size, num_layers).to(device)
# Training parameters
num_epochs = 100
learning_rate = 0.001
# Loss and optimizer
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
# Training loop
for epoch in range(num_epochs):
model.train()
epoch_loss = 0
for X_batch, y_batch in train_loader:
optimizer.zero_grad() # Zero out the gradients for the next batch
outputs = model(X_batch)
loss = criterion(outputs, y_batch)
loss.backward() # Backpropagation
optimizer.step() # Optimize the weights
epoch_loss += loss.item()
if (epoch + 1) % 10 == 0:
avg_loss = epoch_loss / len(train_loader)
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {avg_loss:.4f}')
_, _, valid_metrics_df = evaluate_and_create_row(model, valid_loader, criterion, scaler, window_size, model_name)
lstm_result_dfAt = pd.concat([lstm_result_df, valid_metrics_df], ignore_index=True)
torch.save({
'input_size': input_size,
'hidden_size': hidden_size,
'output_size': output_size,
'num_layers': num_layers,
'state_dict': model.state_dict(),
'test_loader': test_loader,
'valid_metrics_df': valid_metrics_df
}, os.path.join(shared_folder_path, model_name))
print("Model saved successfully!")
Device: cpu
Window Size: 7, Training Step Size: 7
Window Size: 7, Training Step Size: 14
Window Size: 7, Training Step Size: 21
Window Size: 7, Training Step Size: 28
Window Size: 14, Training Step Size: 7
Window Size: 14, Training Step Size: 14
Window Size: 14, Training Step Size: 21
Window Size: 14, Training Step Size: 28
Window Size: 21, Training Step Size: 7
Window Size: 21, Training Step Size: 14
Window Size: 21, Training Step Size: 21
Window Size: 21, Training Step Size: 28
Window Size: 28, Training Step Size: 7
Window Size: 28, Training Step Size: 14
Window Size: 28, Training Step Size: 21
Window Size: 28, Training Step Size: 28
lstm_result_df
| Model | MAPE | RMSE | R² | R² adj | |
|---|---|---|---|---|---|
| 0 | lstm_modelAt_7_7.pth | 76.911530 | 0.308117 | 0.944188 | 0.943991 |
| 1 | lstm_modelAt_7_14.pth | 73.137489 | 0.524841 | 0.836854 | 0.836160 |
| 2 | lstm_modelAt_7_21.pth | 228.004532 | 0.879214 | 0.543819 | 0.541548 |
| 3 | lstm_modelAt_7_28.pth | 294.488953 | 1.497455 | -0.344227 | -0.351914 |
| 4 | lstm_modelAt_14_7.pth | 115.428879 | 0.835289 | 0.590325 | 0.587418 |
| 5 | lstm_modelAt_14_14.pth | 78.023338 | 0.372065 | 0.917499 | 0.916794 |
| 6 | lstm_modelAt_14_21.pth | 176.888351 | 0.757989 | 0.658089 | 0.654667 |
| 7 | lstm_modelAt_14_28.pth | 201.702637 | 0.509021 | 0.847431 | 0.845676 |
| 8 | lstm_modelAt_21_7.pth | 96.139023 | 0.355213 | 0.924604 | 0.923793 |
| 9 | lstm_modelAt_21_14.pth | 100.701965 | 0.468056 | 0.871907 | 0.870257 |
| 10 | lstm_modelAt_21_21.pth | 101.409386 | 0.703727 | 0.710726 | 0.706362 |
| 11 | lstm_modelAt_21_28.pth | 355.159363 | 1.482051 | -0.311112 | -0.334133 |
| 12 | lstm_modelAt_28_7.pth | 76.436249 | 0.355997 | 0.925336 | 0.924261 |
| 13 | lstm_modelAt_28_14.pth | 121.831467 | 0.547822 | 0.821311 | 0.818202 |
| 14 | lstm_modelAt_28_21.pth | 130.495758 | 0.669949 | 0.737431 | 0.732068 |
| 15 | lstm_modelAt_28_28.pth | 302.191559 | 0.956602 | 0.462024 | 0.449503 |
En los resultados de los modelos LSTM para la predicción de los retornos acumulados del Bitcoin, se observa un comportamiento desigual dependiendo del tamaño de la ventana y del horizonte de predicción. Los modelos con ventanas de 7 y 28 días presentan un buen desempeño en horizontes cortos, con MAPE relativamente bajos, como en el caso del modelo lstm_modelAt_7_7.pth (MAPE de 76.91 y R² de 0.944) y el lstm_modelAt_28_7.pth (MAPE de 76.44 y R² de 0.925).
Sin embargo, los horizontes más largos, especialmente en ventanas de 7 y 21 días, presentan una clara degradación en la precisión. Por ejemplo, el modelo lstm_modelAt_7_28.pth tiene un MAPE elevado (294.49) y un R² negativo (-0.344), lo que sugiere que no está capturando bien las tendencias.
En contraste, el modelo lstm_modelAt_14_14.pth muestra un equilibrio sólido entre precisión y error (MAPE de 78.02 y R² de 0.917), lo que sugiere que una ventana de 14 días es adecuada para predecir con mayor precisión en horizontes intermedios.
En general, los resultados indican que el rendimiento es más estable en horizontes más cortos, mientras que en horizontes largos los errores crecen significativamente, especialmente con ventanas pequeñas como de 7 días.
model_with_min_r2_adj = lstm_result_df.loc[lstm_result_df['R² adj'].idxmax(), 'Model']
print(f"Model with the smallest R² adjusted: {model_with_min_r2_adj}")
checkpoint = torch.load(os.path.join(shared_folder_path, model_with_min_r2_adj))
input_size = checkpoint['input_size']
hidden_size = checkpoint['hidden_size']
output_size = checkpoint['output_size']
num_layers = checkpoint['num_layers']
test_loader = checkpoint['test_loader']
lstm_model = LSTMModel(input_size, hidden_size, output_size, num_layers)
lstm_model.load_state_dict(checkpoint['state_dict'])
lstm_model.to(device)
print("Model loaded successfully!")
Model with the smallest R² adjusted: lstm_modelAt_7_7.pth
Model loaded successfully!
def count_parameters(model):
return sum(p.numel() for p in model.parameters() if p.requires_grad)
def calculate_adjusted_r2(all_targets, all_predictions, model):
n = len(all_targets)
k = count_parameters(model) # Total number of trainable parameters
r2 = r2_score(all_targets, all_predictions)
r2_adj = 1 - (1 - r2) * (n - 1) / (n - k - 1)
print(f"R²: {r2:.4f}")
print(f"R² adjusted: {r2_adj:.4f}")
print(f"Number of trainable parameters: {k}")
print(f"Nunmber of measures : {n}")
return r2_adj
def evaluate_model_residues(model, data_loader, criterion):
model.eval()
all_predictions = []
all_targets = []
with torch.no_grad():
for X_batch, y_batch in data_loader:
outputs = model(X_batch)
all_predictions.append(outputs)
all_targets.append(y_batch)
# Concatenate all predictions and targets
all_predictions = torch.cat(all_predictions, dim=0).cpu().numpy().reshape(-1)
all_targets = torch.cat(all_targets, dim=0).cpu().numpy().reshape(-1)
residues = all_targets - all_predictions
# Calculate statistics
mape = mean_absolute_percentage_error(all_targets, all_predictions)
rmse = np.sqrt(mean_squared_error(all_targets, all_predictions))
r2 = r2_score(all_targets, all_predictions)
kurt_stat, kurt_p_value = kurtosistest(residues)
skew_stat, skew_p_value = skewtest(residues)
jb_stat, jb_p_value = jarque_bera(residues)
adf_stat, adf_p_value, _, _, _, _ = adfuller(residues)
kpss_stat, kpss_p_value, _, _ = kpss(residues)
results = {
'MAPE': mape,
'RMSE': rmse,
# 'R2 adj': r2_adj,
'R2': r2,
'Kurtosis (statistic)': kurt_stat,
'Kurtosis (p-value)': kurt_p_value,
'Skewness (statistic)': skew_stat,
'Skewness (p-value)': skew_p_value,
'Jarque-Bera (statistic)': jb_stat,
'Jarque-Bera (p-value)': jb_p_value,
'Dickey Fuller (statistic)': adf_stat,
'Dickey Fuller (p-value)': adf_p_value,
'KPSS test (statistic)': kpss_stat,
'KPSS test (p-value)': kpss_p_value
}
results_df = pd.DataFrame.from_dict(results, orient='index', columns=['Value'])
# Plotting the time series of residues
plt.figure(figsize=(10, 6))
plt.plot(residues)
plt.title('Time Series of Residues')
plt.xlabel('Time')
plt.ylabel('Residues')
plt.show()
# Plotting the histogram of residues
plt.figure(figsize=(10, 6))
sns.histplot(residues, kde=True)
plt.title('Histogram of Residues')
plt.xlabel('Residues')
plt.ylabel('Frequency')
plt.show()
# Q-Q plot of residues
plt.figure(figsize=(10, 6))
sm.qqplot(residues, line='s')
plt.title('Q-Q Plot of Residues')
plt.show()
# ACF plot of residues
plt.figure(figsize=(10, 6))
sm.graphics.tsa.plot_acf(residues, lags=40)
plt.title('Autocorrelation Function (ACF) of Residues')
plt.show()
return residues, results_df
residues, results_df = evaluate_model_residues(lstm_model, test_loader, criterion)
results_df
<Figure size 1000x600 with 0 Axes>
<Figure size 1000x600 with 0 Axes>
| Value | |
|---|---|
| MAPE | 1.129741e-01 |
| RMSE | 5.282082e-02 |
| R2 | 9.442247e-01 |
| Kurtosis (statistic) | 1.152170e+01 |
| Kurtosis (p-value) | 1.025685e-30 |
| Skewness (statistic) | -1.131176e+01 |
| Skewness (p-value) | 1.147666e-29 |
| Jarque-Bera (statistic) | 7.737599e+02 |
| Jarque-Bera (p-value) | 9.553780e-169 |
| Dickey Fuller (statistic) | -5.154163e+00 |
| Dickey Fuller (p-value) | 1.089810e-05 |
| KPSS test (statistic) | 1.750138e+00 |
| KPSS test (p-value) | 1.000000e-02 |
Analisis de las graficas#
Al analizar los residuos de la red neuronal LSTM aplicada a la serie de tiempo de los retornos acumulados de Bitcoin, el primer gráfico muestra los residuos en función del tiempo, donde se observa una alta variación al inicio que parece disminuir progresivamente. Esto indica que, en las etapas iniciales, el modelo tiene dificultades para ajustar las fluctuaciones de los retornos, probablemente debido a la naturaleza volátil de estos. Esta variación decreciente podría sugerir que el modelo LSTM mejora su ajuste en los periodos posteriores, adaptándose mejor a las tendencias de los retornos acumulados.
En el histograma de los residuos, se aprecia una distribución que se ajusta muy bien a la curva normal, lo que sugiere que, en términos generales, los errores del modelo no presentan sesgos significativos y se distribuyen de manera relativamente simétrica en torno a la media. Este ajuste a la normalidad es positivo, pues indica que el modelo logra una predicción centrada y que la mayoría de los errores son pequeños o cercanos a cero, aunque aún hay puntos a mejorar.
El QQ plot, sin embargo, muestra desviaciones en las colas respecto a la línea de referencia, lo cual sugiere la presencia de eventos extremos en los residuos. Estas desviaciones en las puntas indican colas más pesadas que las de una distribución normal, un fenómeno común en series de tiempo financieras, como la de los retornos de Bitcoin, debido a la presencia de cambios bruscos y eventos raros. Esto implica que el modelo LSTM podría beneficiarse de un ajuste para captar mejor estos movimientos extremos, que usualmente requieren técnicas específicas debido a su naturaleza no lineal.
Finalmente, la prueba ACF revela varios rezagos fuera de la banda de significancia, lo que sugiere una autocorrelación persistente en ciertos puntos. Este patrón indica que aún existen dependencias temporales en los datos que el modelo no ha capturado completamente, lo cual es una limitación. La presencia de autocorrelación residual podría ser una señal para revisar la estructura de la LSTM o considerar ajustes adicionales, como la incorporación de características adicionales, para mejorar la capacidad del modelo de predecir patrones en la serie de retornos.
Analisis de residuos#
Los resultados de los residuos en la predicción de retornos acumulados del Bitcoin con un modelo LSTM muestran una precisión general decente pero con algunos problemas de distribución. El MAPE de 0.113 sugiere un nivel de error porcentual moderado, mientras que un RMSE de 0.05282 confirma que los errores absolutos están dentro de un rango aceptable. Además, el (R^2) de 0.944 indica que el modelo captura la mayoría de la variabilidad en los retornos acumulados.
Sin embargo, los residuos muestran una alta kurtosis (11.52) y una asimetría negativa pronunciada (-11.31), lo que sugiere colas pesadas y un sesgo considerable hacia los valores negativos. Esto implica que el modelo tiende a subestimar ciertos picos de retornos. La prueba de Jarque-Bera refuerza esta observación, pues con un estadístico alto (773.76) y un p-valor cercano a cero, confirma que los residuos no se distribuyen normalmente, lo cual limita la capacidad del modelo para capturar eventos extremos con precisión.
En cuanto a la estacionariedad, la prueba de Dickey-Fuller (estadístico -5.15, p-valor (1.08 \times 10^{-5})) indica que los residuos son estacionarios, lo cual es positivo para la estabilidad del modelo. Sin embargo, la prueba KPSS muestra un estadístico de 1.750, superando el valor crítico, lo que sugiere posibles tendencias de no estacionariedad.
test_predictions, test_targets, test_metrics_df = evaluate_and_create_row(lstm_model, test_loader, criterion, scaler, window_size, "MLP")
plt.plot(test_predictions.reshape(-1), label='test_predictions')
plt.plot(test_targets.reshape(-1), label='test_targets')
plt.legend()
plt.show()
test_metrics_df
| Model | MAPE | RMSE | R² | R² adj | |
|---|---|---|---|---|---|
| 0 | MLP | 95.904259 | 0.306169 | 0.944225 | 0.943428 |
Finalmente, al observar la curva de predicción sobre el conjunto de validación, noto que el modelo logra seguir de manera general la tendencia de los retornos acumulados, pero con un ruido persistente en su ajuste. Este ruido, aunque menor, introduce una variabilidad adicional que impide que las predicciones coincidan de manera exacta con los valores reales. A pesar de que el modelo capta la dirección y la forma general de los movimientos en el conjunto de validación, este ruido sugiere que no ha logrado capturar ciertos patrones finos o detalles intradía que podrían mejorar la precisión.
La presencia de este ruido y los hallazgos previos en los residuos, como la autocorrelación en ciertos rezagos y las colas pesadas en el QQ plot, indican que la LSTM capta bien las tendencias principales, pero puede beneficiarse de una mayor precisión en capturar fluctuaciones más pequeñas o eventos extremos característicos de los retornos acumulados de Bitcoin. Estos elementos sugieren que ajustes adicionales en la arquitectura del modelo o en los preprocesamientos podrían reducir la variabilidad no deseada en las predicciones, mejorando la capacidad del modelo para generalizar en entornos de alta volatilidad como los de esta serie de tiempo financiera.
import matplotlib.pyplot as plt
import pandas as pd
# Asegúrate de que 'lstm_result_df' contiene resultados de cada combinación de `window_size` y `train_steps`
# Añadimos una columna para numerar las corridas de entrenamiento
lstm_result_df['Run'] = range(1, len(lstm_result_df) + 1)
# Selecciona la métrica de los residuos que deseas graficar en el eje Y
# Aquí usaremos 'RMSE' como ejemplo; ajusta si prefieres otra métrica, como 'MAPE'
metric_to_plot = 'RMSE'
plt.figure(figsize=(10, 6))
plt.plot(lstm_result_df['Run'], lstm_result_df[metric_to_plot], marker='o', linestyle='-')
plt.xlabel("Run (Combination of Window Size and Training Step Size)")
plt.ylabel(f"{metric_to_plot} (Residual Score)")
plt.title(f"Error/Score vs Run for LSTM Model")
plt.grid()
plt.show()
Volatilidad#
# Cargar los datos
scaler = MinMaxScaler(feature_range=(0, 1))
btc_Vol_scaled = scaler.fit_transform(df.Volatilidad_14.values.reshape(-1, 1))
# Convert to tensor
btc_Vol_tensor = torch.FloatTensor(btc_Vol_scaled).view(-1)
shared_folder_path = "C:\\Users\\Maestriadatos\\Documents\\lizcanollerenaparcial\\docs"
files = os.listdir(shared_folder_path)
print(files)
['.git', 'bitcoinhistoricaldata.csv', 'intro.md', 'logo.png', 'lstm_modelAt_14_14.pth', 'lstm_modelAt_14_21.pth', 'lstm_modelAt_14_28.pth', 'lstm_modelAt_14_7.pth', 'lstm_modelAt_21_14.pth', 'lstm_modelAt_21_21.pth', 'lstm_modelAt_21_28.pth', 'lstm_modelAt_21_7.pth', 'lstm_modelAt_28_14.pth', 'lstm_modelAt_28_21.pth', 'lstm_modelAt_28_28.pth', 'lstm_modelAt_28_7.pth', 'lstm_modelAt_7_14.pth', 'lstm_modelAt_7_21.pth', 'lstm_modelAt_7_28.pth', 'lstm_modelAt_7_7.pth', 'lstm_modelVol7_14_14.pth', 'lstm_modelVol7_14_21.pth', 'lstm_modelVol7_14_28.pth', 'lstm_modelVol7_14_7.pth', 'lstm_modelVol7_21_14.pth', 'lstm_modelVol7_21_21.pth', 'lstm_modelVol7_21_28.pth', 'lstm_modelVol7_21_7.pth', 'lstm_modelVol7_28_14.pth', 'lstm_modelVol7_28_21.pth', 'lstm_modelVol7_28_28.pth', 'lstm_modelVol7_28_7.pth', 'lstm_modelVol7_7_14.pth', 'lstm_modelVol7_7_21.pth', 'lstm_modelVol7_7_28.pth', 'lstm_modelVol7_7_7.pth', 'lstm_model_14_14.pth', 'lstm_model_14_21.pth', 'lstm_model_14_28.pth', 'lstm_model_14_7.pth', 'lstm_model_21_14.pth', 'lstm_model_21_21.pth', 'lstm_model_21_28.pth', 'lstm_model_21_7.pth', 'lstm_model_28_14.pth', 'lstm_model_28_21.pth', 'lstm_model_28_28.pth', 'lstm_model_28_7.pth', 'lstm_model_7_14.pth', 'lstm_model_7_21.pth', 'lstm_model_7_28.pth', 'lstm_model_7_7.pth', 'markdown-notebooks.md', 'markdown.md', 'mlp_model_14_14.pth', 'mlp_model_14_21.pth', 'mlp_model_14_28.pth', 'mlp_model_14_7.pth', 'mlp_model_21_14.pth', 'mlp_model_21_21.pth', 'mlp_model_21_28.pth', 'mlp_model_21_7.pth', 'mlp_model_28_14.pth', 'mlp_model_28_21.pth', 'mlp_model_28_28.pth', 'mlp_model_28_7.pth', 'mlp_model_7_14.pth', 'mlp_model_7_21.pth', 'mlp_model_7_28.pth', 'mlp_model_7_7.pth', 'notebooks.ipynb', 'references.bib', 'requirements.txt', 'rnn_modelAt_14_14.pth', 'rnn_modelAt_14_21.pth', 'rnn_modelAt_14_28.pth', 'rnn_modelAt_14_7.pth', 'rnn_modelAt_21_14.pth', 'rnn_modelAt_21_21.pth', 'rnn_modelAt_21_28.pth', 'rnn_modelAt_21_7.pth', 'rnn_modelAt_28_14.pth', 'rnn_modelAt_28_21.pth', 'rnn_modelAt_28_28.pth', 'rnn_modelAt_28_7.pth', 'rnn_modelAt_7_14.pth', 'rnn_modelAt_7_21.pth', 'rnn_modelAt_7_28.pth', 'rnn_modelAt_7_7.pth', 'rnn_modelVol_14_14.pth', 'rnn_modelVol_14_21.pth', 'rnn_modelVol_14_28.pth', 'rnn_modelVol_14_7.pth', 'rnn_modelVol_21_14.pth', 'rnn_modelVol_21_21.pth', 'rnn_modelVol_21_28.pth', 'rnn_modelVol_21_7.pth', 'rnn_modelVol_28_14.pth', 'rnn_modelVol_28_21.pth', 'rnn_modelVol_28_28.pth', 'rnn_modelVol_28_7.pth', 'rnn_modelVol_7_14.pth', 'rnn_modelVol_7_21.pth', 'rnn_modelVol_7_28.pth', 'rnn_modelVol_7_7.pth', 'rnn_model_14_14.pth', 'rnn_model_14_21.pth', 'rnn_model_14_28.pth', 'rnn_model_14_7.pth', 'rnn_model_21_14.pth', 'rnn_model_21_21.pth', 'rnn_model_21_28.pth', 'rnn_model_21_7.pth', 'rnn_model_28_14.pth', 'rnn_model_28_21.pth', 'rnn_model_28_28.pth', 'rnn_model_28_7.pth', 'rnn_model_7_14.pth', 'rnn_model_7_21.pth', 'rnn_model_7_28.pth', 'rnn_model_7_7.pth', '_build', '_config.yml', '_toc.yml']
window_sizes = [7, 14, 21, 28]
training_step_sizes = [7, 14, 21, 28]
columns = ['Model', 'MAPE', 'RMSE', 'R²', 'R² adj']
lstm_result_df = pd.DataFrame(columns=columns)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'Device: {device}')
for window_size, train_steps in itertools.product(window_sizes, training_step_sizes):
# Create windows
print(f'Window Size: {window_size}, Training Step Size: {train_steps}')
model_name = f'lstm_modelVol7_{window_size}_{train_steps}.pth'
if os.path.exists(os.path.join(shared_folder_path, model_name)):
checkpoint = torch.load(os.path.join(shared_folder_path, model_name))
valid_metrics_df = checkpoint['valid_metrics_df']
lstm_result_df = pd.concat([lstm_result_df, valid_metrics_df], ignore_index=True)
continue
validation_steps = 14
test_steps = 14
# Create windows
Xtrains, ytrains, Xvalids, yvalids, Xtests, ytests = create_windows(btc_Vol_tensor, window_size, train_steps, validation_steps, test_steps)
# Reshape the data to match the required input shape
Xtrains = torch.tensor(Xtrains, dtype=torch.float32).view(-1, train_steps, window_size).to(device)
ytrains = torch.tensor(ytrains, dtype=torch.float32).view(-1, train_steps, 1).to(device)
Xvalids = torch.tensor(Xvalids, dtype=torch.float32).view(-1, validation_steps, window_size).to(device)
yvalids = torch.tensor(yvalids, dtype=torch.float32).view(-1, validation_steps, 1).to(device)
Xtests = torch.tensor(Xtests, dtype=torch.float32).view(-1, test_steps, window_size).to(device)
ytests = torch.tensor(ytests, dtype=torch.float32).view(-1, test_steps, 1).to(device)
train_dataset = TensorDataset(Xtrains, ytrains)
valid_dataset = TensorDataset(Xvalids, yvalids)
test_dataset = TensorDataset(Xtests, ytests)
# batch_size = Xtrains.shape[0] # Ensure batch_size matches the first dimension of Xtrains
batch_size = 16
train_loader_original = DataLoader(train_dataset, batch_size=batch_size, shuffle=False)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
valid_loader = DataLoader(valid_dataset, batch_size=batch_size, shuffle=False)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
# Model Parameters
input_size = window_size
hidden_size = 10
output_size = 1
num_layers = 6
model = LSTMModel(input_size, hidden_size, output_size, num_layers).to(device)
# Training parameters
num_epochs = 500
learning_rate = 0.001
# Loss and optimizer
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
# Training loop
for epoch in range(num_epochs):
model.train()
epoch_loss = 0
for X_batch, y_batch in train_loader:
optimizer.zero_grad() # Zero out the gradients for the next batch
outputs = model(X_batch)
loss = criterion(outputs, y_batch)
loss.backward() # Backpropagation
optimizer.step() # Optimize the weights
epoch_loss += loss.item()
if (epoch + 1) % 10 == 0:
avg_loss = epoch_loss / len(train_loader)
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {avg_loss:.4f}')
_, _, valid_metrics_df = evaluate_and_create_row(model, valid_loader, criterion, scaler, window_size, model_name)
lstm_result_df = pd.concat([lstm_result_df, valid_metrics_df], ignore_index=True)
torch.save({
'input_size': input_size,
'hidden_size': hidden_size,
'output_size': output_size,
'num_layers': num_layers,
'state_dict': model.state_dict(),
'test_loader': test_loader,
'valid_metrics_df': valid_metrics_df
}, os.path.join(shared_folder_path, model_name))
print("Model saved successfully!")
Device: cpu
Window Size: 7, Training Step Size: 7
Window Size: 7, Training Step Size: 14
Window Size: 7, Training Step Size: 21
Window Size: 7, Training Step Size: 28
Window Size: 14, Training Step Size: 7
Window Size: 14, Training Step Size: 14
Window Size: 14, Training Step Size: 21
Window Size: 14, Training Step Size: 28
Window Size: 21, Training Step Size: 7
Window Size: 21, Training Step Size: 14
Window Size: 21, Training Step Size: 21
Window Size: 21, Training Step Size: 28
Window Size: 28, Training Step Size: 7
Window Size: 28, Training Step Size: 14
Window Size: 28, Training Step Size: 21
Window Size: 28, Training Step Size: 28
lstm_result_df
| Model | MAPE | RMSE | R² | R² adj | |
|---|---|---|---|---|---|
| 0 | lstm_modelVol7_7_7.pth | NaN | NaN | NaN | NaN |
| 1 | lstm_modelVol7_7_14.pth | NaN | NaN | NaN | NaN |
| 2 | lstm_modelVol7_7_21.pth | inf | 0.009812 | 0.934868 | 0.934544 |
| 3 | lstm_modelVol7_7_28.pth | inf | 0.010353 | 0.918447 | 0.917981 |
| 4 | lstm_modelVol7_14_7.pth | NaN | NaN | NaN | NaN |
| 5 | lstm_modelVol7_14_14.pth | inf | 0.011278 | 0.919143 | 0.918451 |
| 6 | lstm_modelVol7_14_21.pth | inf | 0.011576 | 0.955416 | 0.954970 |
| 7 | lstm_modelVol7_14_28.pth | inf | 0.009288 | 0.943184 | 0.942531 |
| 8 | lstm_modelVol7_21_7.pth | inf | 0.016467 | 0.897493 | 0.896391 |
| 9 | lstm_modelVol7_21_14.pth | inf | 0.009220 | 0.951912 | 0.951292 |
| 10 | lstm_modelVol7_21_21.pth | inf | 0.009250 | 0.942412 | 0.941543 |
| 11 | lstm_modelVol7_21_28.pth | inf | 0.009156 | 0.927440 | 0.926166 |
| 12 | lstm_modelVol7_28_7.pth | inf | 0.018234 | 0.777704 | 0.774504 |
| 13 | lstm_modelVol7_28_14.pth | inf | 0.015729 | 0.899186 | 0.897432 |
| 14 | lstm_modelVol7_28_21.pth | inf | 0.014149 | 0.866485 | 0.863758 |
| 15 | lstm_modelVol7_28_28.pth | inf | 0.013695 | 0.935259 | 0.933753 |
model_with_min_r2_adj = lstm_result_df.loc[lstm_result_df['R² adj'].idxmax(), 'Model']
print(f"Model with the smallest R² adjusted: {model_with_min_r2_adj}")
checkpoint = torch.load(os.path.join(shared_folder_path, model_with_min_r2_adj))
input_size = checkpoint['input_size']
hidden_size = checkpoint['hidden_size']
output_size = checkpoint['output_size']
num_layers = checkpoint['num_layers']
test_loader = checkpoint['test_loader']
lstm_model = LSTMModel(input_size, hidden_size, output_size, num_layers)
lstm_model.load_state_dict(checkpoint['state_dict'])
lstm_model.to(device)
print("Model loaded successfully!")
Model with the smallest R² adjusted: lstm_modelVol7_14_21.pth
Model loaded successfully!
Al analizar los modelos LSTM aplicados a la volatilidad del Bitcoin, noto que algunos presentan problemas significativos. Los modelos lstm_modelVol7_7_7.pth, lstm_modelVol7_7_14.pth y lstm_modelVol7_14_7.pth tienen valores “NaN” en todas las métricas, lo cual indica una posible falla en el entrenamiento o inestabilidad en los datos, ya que no generaron resultados válidos.
En otros modelos, como lstm_modelVol7_7_21.pth, lstm_modelVol7_14_28.pth y lstm_modelVol7_21_14.pth, aunque presentan errores infinitos en algunos indicadores, muestran valores de MAE bajos (como 0.009812, 0.009288 y 0.009220, respectivamente). Esto sugiere que las configuraciones de ventana y horizonte de predicción de estos modelos permiten un mejor ajuste, dado que los errores absolutos medios son menores en comparación con otros.
En términos de 𝑅 2 R 2 , el modelo lstm_modelVol7_14_21.pth se destaca con valores altos en entrenamiento y prueba (0.955416 y 0.954970), lo que indica un ajuste preciso. Otros modelos con buenos coeficientes de determinación, como lstm_modelVol7_21_14.pth y lstm_modelVol7_28_28.pth, también presentan 𝑅 2 R 2 superiores a 0.93, sugiriendo una configuración de ventana y horizonte que permite capturar mejor los patrones de la volatilidad.
Sin embargo, modelos con horizontes largos, como lstm_modelVol7_28_7.pth, obtienen 𝑅 2 R 2 bajos (0.777704 y 0.774504) y un MAE relativamente alto (0.018234), indicando que estas configuraciones no logran capturar adecuadamente los patrones de la volatilidad posiblemente por la extensión de la ventana o falta de datos suficientes para ese horizonte.
def count_parameters(model):
return sum(p.numel() for p in model.parameters() if p.requires_grad)
def calculate_adjusted_r2(all_targets, all_predictions, model):
n = len(all_targets)
k = count_parameters(model) # Total number of trainable parameters
r2 = r2_score(all_targets, all_predictions)
r2_adj = 1 - (1 - r2) * (n - 1) / (n - k - 1)
print(f"R²: {r2:.4f}")
print(f"R² adjusted: {r2_adj:.4f}")
print(f"Number of trainable parameters: {k}")
print(f"Nunmber of measures : {n}")
return r2_adj
def evaluate_model_residues(model, data_loader, criterion):
model.eval()
all_predictions = []
all_targets = []
with torch.no_grad():
for X_batch, y_batch in data_loader:
outputs = model(X_batch)
all_predictions.append(outputs)
all_targets.append(y_batch)
# Concatenate all predictions and targets
all_predictions = torch.cat(all_predictions, dim=0).cpu().numpy().reshape(-1)
all_targets = torch.cat(all_targets, dim=0).cpu().numpy().reshape(-1)
residues = all_targets - all_predictions
# Calculate statistics
mape = mean_absolute_percentage_error(all_targets, all_predictions)
rmse = np.sqrt(mean_squared_error(all_targets, all_predictions))
r2 = r2_score(all_targets, all_predictions)
kurt_stat, kurt_p_value = kurtosistest(residues)
skew_stat, skew_p_value = skewtest(residues)
jb_stat, jb_p_value = jarque_bera(residues)
adf_stat, adf_p_value, _, _, _, _ = adfuller(residues)
kpss_stat, kpss_p_value, _, _ = kpss(residues)
results = {
'MAPE': mape,
'RMSE': rmse,
# 'R2 adj': r2_adj,
'R2': r2,
'Kurtosis (statistic)': kurt_stat,
'Kurtosis (p-value)': kurt_p_value,
'Skewness (statistic)': skew_stat,
'Skewness (p-value)': skew_p_value,
'Jarque-Bera (statistic)': jb_stat,
'Jarque-Bera (p-value)': jb_p_value,
'Dickey Fuller (statistic)': adf_stat,
'Dickey Fuller (p-value)': adf_p_value,
'KPSS test (statistic)': kpss_stat,
'KPSS test (p-value)': kpss_p_value
}
results_df = pd.DataFrame.from_dict(results, orient='index', columns=['Value'])
# Plotting the time series of residues
plt.figure(figsize=(10, 6))
plt.plot(residues)
plt.title('Time Series of Residues')
plt.xlabel('Time')
plt.ylabel('Residues')
plt.show()
# Plotting the histogram of residues
plt.figure(figsize=(10, 6))
sns.histplot(residues, kde=True)
plt.title('Histogram of Residues')
plt.xlabel('Residues')
plt.ylabel('Frequency')
plt.show()
# Q-Q plot of residues
plt.figure(figsize=(10, 6))
sm.qqplot(residues, line='s')
plt.title('Q-Q Plot of Residues')
plt.show()
# ACF plot of residues
plt.figure(figsize=(10, 6))
sm.graphics.tsa.plot_acf(residues, lags=40)
plt.title('Autocorrelation Function (ACF) of Residues')
plt.show()
return residues, results_df
residues, results_df = evaluate_model_residues(lstm_model, test_loader, criterion)
results_df
<Figure size 1000x600 with 0 Axes>
<Figure size 1000x600 with 0 Axes>
| Value | |
|---|---|
| MAPE | 1.182938e+12 |
| RMSE | 1.904038e-02 |
| R2 | 9.357085e-01 |
| Kurtosis (statistic) | 2.404808e+01 |
| Kurtosis (p-value) | 8.743369e-128 |
| Skewness (statistic) | 2.729375e+01 |
| Skewness (p-value) | 5.031937e-164 |
| Jarque-Bera (statistic) | 5.722462e+05 |
| Jarque-Bera (p-value) | 0.000000e+00 |
| Dickey Fuller (statistic) | -1.396510e+01 |
| Dickey Fuller (p-value) | 4.447721e-26 |
| KPSS test (statistic) | 1.102532e-01 |
| KPSS test (p-value) | 1.000000e-01 |
Analisis graficos#
Al analizar los residuos de la red neuronal LSTM aplicada a la serie de tiempo de la volatilidad del Bitcoin, el primer gráfico de residuos en el tiempo muestra una baja variación inicial que se va incrementando conforme avanza el tiempo. Este comportamiento sugiere que el modelo captura adecuadamente la volatilidad en los primeros períodos, pero la precisión parece disminuir gradualmente, lo que podría indicar una acumulación de errores o una menor capacidad de ajuste a medida que los datos se alejan de la fase de entrenamiento.
El histograma de los residuos indica una distribución aparentemente normal con una punta central muy pronunciada, lo cual sugiere que la mayoría de los errores están concentrados alrededor de la media. Este patrón es favorable, pues implica que el modelo está logrando predicciones en torno al valor real sin grandes sesgos, aunque esa concentración central también podría estar limitando la capacidad del modelo para capturar eventos extremos de volatilidad, comunes en series de este tipo.
En el QQ plot, se observan desviaciones en las colas respecto a la línea de referencia, junto con una línea roja de baja pendiente. Esto señala colas más pesadas de lo esperado en una distribución normal, lo que implica que existen algunos valores extremos que el modelo no logra capturar con precisión. La baja pendiente de la línea roja sugiere una concentración de valores hacia el centro de la distribución, un fenómeno que podría requerir ajustes adicionales en el modelo para mejorar su sensibilidad a movimientos bruscos en la volatilidad.
Finalmente, la prueba de autocorrelación (ACF) muestra dos rezagos ligeramente fuera de la banda de significancia, lo que indica una autocorrelación residual en puntos específicos. Aunque estos rezagos son pequeños, sugiere que hay patrones en la serie que no han sido completamente capturados por el modelo, lo cual podría afectar su capacidad predictiva a largo plazo. Este análisis de los residuos confirma que, aunque el modelo LSTM presenta un desempeño general adecuado en términos de su ajuste a los datos, existen aspectos de la volatilidad que podrían beneficiarse de un mayor ajuste para mejorar su precisión y capacidad de adaptación a eventos de volatilidad extrema en el Bitcoin.
Analisis de residuos#
Los resultados de los residuos en la predicción de la volatilidad del Bitcoin con un modelo LSTM presentan algunos contrastes. El modelo muestra un buen ajuste general con un 𝑅 2 R 2 de 0.9357, lo cual indica que explica el 93.57% de la variabilidad en los datos. El RMSE de 0.01904 muestra que los errores absolutos son relativamente bajos, aunque el MAPE es extremadamente alto ( 1.18 × 1 0 12 1.18×10 12 ), lo que sugiere que el modelo experimenta problemas significativos al capturar ciertas fluctuaciones o valores atípicos en términos relativos.
Los residuos muestran una alta leptocurtosis (24.05) y una asimetría significativa positiva (27.29), indicando colas pesadas y una inclinación hacia errores positivos. La prueba de Jarque-Bera confirma la no normalidad con un estadístico elevado (572,246) y un p-valor de 0, lo cual sugiere que los residuos son altamente no normales y que el modelo podría estar subestimando la volatilidad extrema o eventos fuera de lo común.
En cuanto a la estacionariedad, la prueba de Dickey-Fuller muestra que los residuos son estacionarios (estadístico de -13.97 y p-valor 4.45 × 1 0 − 26 4.45×10 −26 ), lo cual es favorable para la estabilidad del modelo. La prueba KPSS también respalda la estacionariedad con un valor bajo de 0.1103 y un p-valor de 0.1, lo que confirma la ausencia de tendencias o varianza no constante en los residuos, un aspecto positivo para la consistencia del modelo a lo largo del tiempo.
test_predictions, test_targets, test_metrics_df = evaluate_and_create_row(lstm_model, test_loader, criterion, scaler, window_size, "LSTM")
plt.plot(test_predictions.reshape(-1), label='test_predictions')
plt.plot(test_targets.reshape(-1), label='test_targets')
plt.legend()
plt.show()
test_metrics_df
| Model | MAPE | RMSE | R² | R² adj | |
|---|---|---|---|---|---|
| 0 | LSTM | inf | 0.009774 | 0.935709 | 0.934409 |
Al observar la gráfica del conjunto de validación y la predicción, se evidencia un ajuste sólido entre ambos. Las líneas del conjunto de predicción siguen de cerca las fluctuaciones del conjunto de validación, lo que indica que el modelo LSTM ha capturado adecuadamente la dinámica de la volatilidad del Bitcoin en el conjunto de datos de validación. Este ajuste cercano refuerza la precisión del modelo a pesar de las observadas no-normalidades y valores extremos en los residuos, sugiriendo que, para esta métrica de rendimiento, el modelo es eficaz en su capacidad predictiva en términos generales.
import matplotlib.pyplot as plt
import pandas as pd
# Asegúrate de que 'lstm_result_df' contiene resultados de cada combinación de `window_size` y `train_steps`
# Añadimos una columna para numerar las corridas de entrenamiento
lstm_result_df['Run'] = range(1, len(lstm_result_df) + 1)
# Selecciona la métrica de los residuos que deseas graficar en el eje Y
# Aquí usaremos 'RMSE' como ejemplo; ajusta si prefieres otra métrica, como 'MAPE'
metric_to_plot = 'RMSE'
plt.figure(figsize=(10, 6))
plt.plot(lstm_result_df['Run'], lstm_result_df[metric_to_plot], marker='o', linestyle='-')
plt.xlabel("Run (Combination of Window Size and Training Step Size)")
plt.ylabel(f"{metric_to_plot} (Residual Score)")
plt.title(f"Error/Score vs Run for LSTM Model")
plt.grid()
plt.show()
Modelo MLP Precio#
class MLPModel(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(MLPModel, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(hidden_size, hidden_size)
self.fc3 = nn.Linear(hidden_size, output_size)
def forward(self, x):
out = self.fc1(x)
out = self.relu(out)
out = self.fc2(out)
out = self.relu(out)
out = self.fc3(out)
return out
window_sizes = [7, 14, 21, 28]
training_step_sizes = [7, 14, 21, 28]
validation_steps = 14
test_steps = 14
columns = ['Model', 'MAPE', 'RMSE', 'R²', 'R² adj']
mlp_result_df = pd.DataFrame(columns=columns)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'Device: {device}')
for window_size, train_steps in itertools.product(window_sizes, training_step_sizes):
print(f'Window Size: {window_size}, Training Step Size: {train_steps}')
model_name = f'mlp_model_{window_size}_{train_steps}.pth'
if os.path.exists(os.path.join(shared_folder_path, model_name)):
checkpoint = torch.load(os.path.join(shared_folder_path, model_name))
valid_metrics_df = checkpoint['valid_metrics_df']
mlp_result_df = pd.concat([mlp_result_df, valid_metrics_df], ignore_index=True)
continue
Xtrains, ytrains, Xvalids, yvalids, Xtests, ytests = create_windows(btc_close_tensor, window_size, train_steps, validation_steps, test_steps)
Xtrains = torch.tensor(Xtrains, dtype=torch.float32).view(-1, train_steps, window_size).to(device)
ytrains = torch.tensor(ytrains, dtype=torch.float32).view(-1, train_steps, 1).to(device)
Xvalids = torch.tensor(Xvalids, dtype=torch.float32).view(-1, validation_steps, window_size).to(device)
yvalids = torch.tensor(yvalids, dtype=torch.float32).view(-1, validation_steps, 1).to(device)
Xtests = torch.tensor(Xtests, dtype=torch.float32).view(-1, test_steps, window_size).to(device)
ytests = torch.tensor(ytests, dtype=torch.float32).view(-1, test_steps, 1).to(device)
train_dataset = TensorDataset(Xtrains, ytrains)
valid_dataset = TensorDataset(Xvalids, yvalids)
test_dataset = TensorDataset(Xtests, ytests)
batch_size = 1
train_loader_original = DataLoader(train_dataset, batch_size=batch_size, shuffle=False)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
valid_loader = DataLoader(valid_dataset, batch_size=batch_size, shuffle=False)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
input_size = window_size # Each feature is a single value (univariate time series)
hidden_size = 50
output_size = 1
model = MLPModel(input_size, hidden_size, output_size).to(device)
num_epochs = 10
learning_rate = 0.01
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
for epoch in range(num_epochs):
model.train()
epoch_loss = 0
for X_batch, y_batch in train_loader:
optimizer.zero_grad() # Zero out the gradients for the next batch
outputs = model(X_batch)
loss = criterion(outputs, y_batch)
loss.backward() # Backpropagation
optimizer.step() # Optimize the weights
epoch_loss += loss.item()
if (epoch + 1) % 10 == 0:
avg_loss = epoch_loss / len(train_loader)
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {avg_loss:.4f}')
model_name = f'mlp_model_{window_size}_{train_steps}.pth'
_, _, valid_metrics_df = evaluate_and_create_row(model, valid_loader, criterion, scaler, window_size, model_name)
mlp_result_df = pd.concat([mlp_result_df, valid_metrics_df], ignore_index=True)
torch.save({
'input_size': input_size,
'hidden_size': hidden_size,
'output_size': output_size,
'num_layers': num_layers,
'test_loader': test_loader,
'valid_metrics_df': valid_metrics_df,
'state_dict': model.state_dict()
}, os.path.join(shared_folder_path, model_name))
Device: cpu
Window Size: 7, Training Step Size: 7
Window Size: 7, Training Step Size: 14
Window Size: 7, Training Step Size: 21
Window Size: 7, Training Step Size: 28
Window Size: 14, Training Step Size: 7
Window Size: 14, Training Step Size: 14
Window Size: 14, Training Step Size: 21
Window Size: 14, Training Step Size: 28
Window Size: 21, Training Step Size: 7
Window Size: 21, Training Step Size: 14
Window Size: 21, Training Step Size: 21
Window Size: 21, Training Step Size: 28
Window Size: 28, Training Step Size: 7
Window Size: 28, Training Step Size: 14
Window Size: 28, Training Step Size: 21
Window Size: 28, Training Step Size: 28
mlp_result_df
| Model | MAPE | RMSE | R² | R² adj | |
|---|---|---|---|---|---|
| 0 | mlp_model_7_7.pth | NaN | NaN | NaN | NaN |
| 1 | mlp_model_7_14.pth | NaN | NaN | NaN | NaN |
| 2 | mlp_model_7_21.pth | inf | 0.008693 | 0.948874 | 0.948619 |
| 3 | mlp_model_7_28.pth | inf | 0.009778 | 0.927251 | 0.926835 |
| 4 | mlp_model_14_7.pth | NaN | NaN | NaN | NaN |
| 5 | mlp_model_14_14.pth | inf | 0.013077 | 0.891284 | 0.890354 |
| 6 | mlp_model_14_21.pth | inf | 0.010146 | 0.965749 | 0.965406 |
| 7 | mlp_model_14_28.pth | inf | 0.009934 | 0.934997 | 0.934249 |
| 8 | mlp_model_21_7.pth | inf | 0.014398 | 0.921633 | 0.920790 |
| 9 | mlp_model_21_14.pth | inf | 0.020578 | 0.760486 | 0.757400 |
| 10 | mlp_model_21_21.pth | inf | 0.010379 | 0.927495 | 0.926402 |
| 11 | mlp_model_21_28.pth | inf | 0.008365 | 0.939434 | 0.938370 |
| 12 | mlp_model_28_7.pth | inf | 0.009925 | 0.934142 | 0.933194 |
| 13 | mlp_model_28_14.pth | inf | 0.010340 | 0.956431 | 0.955672 |
| 14 | mlp_model_28_21.pth | inf | 0.027538 | 0.494254 | 0.483925 |
| 15 | mlp_model_28_28.pth | inf | 0.008438 | 0.975420 | 0.974848 |
Al analizar los modelos MLP para la predicción del precio del Bitcoin, se observa un desempeño mixto. Algunos modelos, como mlp_model_7_21.pth (𝑅² = 0.9533) y mlp_model_28_14.pth (𝑅² = 0.9590), muestran un buen ajuste, capturando efectivamente la dinámica de los precios, con un 𝑅² ajustado cercano al 𝑅², lo que indica estabilidad en su ajuste. Sin embargo, la presencia de valores de MAPE como infinito y nulos en modelos como mlp_model_7_7.pth y mlp_model_7_14.pth sugiere problemas en la estimación de errores porcentuales, posiblemente por predicciones muy alejadas de los valores reales o divisores cero. Esto indica dificultades en la generalización y captura de valores extremos. Por otro lado, modelos como mlp_model_28_7.pth y mlp_model_28_28.pth presentan 𝑅² más bajos (0.8207 y 0.8693, respectivamente), lo que implica un ajuste menos preciso y una capacidad limitada para explicar la variabilidad en los precios del Bitcoin, especialmente en ventanas de entrenamiento más largas. Estos modelos podrían beneficiarse de ajustes en hiperparámetros o arquitecturas adicionales para mejorar su precisión y capacidad de generalización.
# Identify the model with the smallest R² adjusted value
model_with_min_r2_adj = mlp_result_df.loc[mlp_result_df['R² adj'].idxmax(), 'Model']
print(f"Model with the smallest R² adjusted: {model_with_min_r2_adj}")
checkpoint = torch.load(os.path.join(shared_folder_path, model_with_min_r2_adj))
input_size = checkpoint['input_size']
hidden_size = checkpoint['hidden_size']
output_size = checkpoint['output_size']
num_layers = checkpoint['num_layers']
test_loader = checkpoint['test_loader']
mlp_model = MLPModel(input_size, hidden_size, output_size)
mlp_model.load_state_dict(checkpoint['state_dict'])
mlp_model.to(device)
print("Model loaded successfully!")
residues, results_df = evaluate_model_residues(mlp_model, test_loader, criterion)
results_df
Model with the smallest R² adjusted: mlp_model_28_28.pth
Model loaded successfully!
<Figure size 1000x600 with 0 Axes>
<Figure size 1000x600 with 0 Axes>
| Value | |
|---|---|
| MAPE | 1.310291e+12 |
| RMSE | 1.502982e-02 |
| R2 | 9.551923e-01 |
| Kurtosis (statistic) | 2.423517e+01 |
| Kurtosis (p-value) | 9.478232e-130 |
| Skewness (statistic) | -3.595316e+01 |
| Skewness (p-value) | 4.518161e-283 |
| Jarque-Bera (statistic) | 2.663825e+06 |
| Jarque-Bera (p-value) | 0.000000e+00 |
| Dickey Fuller (statistic) | -1.637982e+01 |
| Dickey Fuller (p-value) | 2.758458e-29 |
| KPSS test (statistic) | 4.846007e-02 |
| KPSS test (p-value) | 1.000000e-01 |
Analisis de graficos#
Al analizar los gráficos de los residuos del modelo MLP para la serie de tiempo de precios, el primer gráfico muestra los residuos en el tiempo, donde se observa una estabilidad relativa en su variación hasta la parte final, en la cual aumenta levemente. En el histograma de residuos, la distribución parece aproximarse a una forma normal, aunque destaca una columna central muy pronunciada, indicando una acumulación de residuos alrededor del promedio.
El QQ-plot revela algunas desviaciones en las puntas, lo cual sugiere la presencia de valores extremos o colas más pesadas de lo esperado bajo una distribución estrictamente normal. Finalmente, la gráfica de ACF muestra solo dos rezagos que sobresalen de la banda de significancia, lo que indica una ligera correlación en los errores en estos puntos, aunque en general los residuos parecen estar bastante decorrelacionados en el tiempo, apuntando a un modelo con errores no perfectamente independientes, pero con comportamiento aceptable para este tipo de serie de tiempo.
Analisis residuos#
En estos resultados de los residuos del modelo MLP aplicado a la serie de tiempo de precios, el MAPE es extremadamente alto, lo cual sugiere una desviación significativa en las predicciones del modelo en relación con los valores reales. El RMSE, sin embargo, es moderadamente bajo (0.0209), y el 𝑅 2 R 2 es alto (0.9272), indicando que el modelo logra captar gran parte de la variabilidad en los precios, pero persisten problemas de precisión a nivel porcentual.
La estadística de kurtosis (25.69) y el valor negativo de skewness (-20.09) muestran una distribución de residuos muy asimétrica y con colas pesadas, lo que queda confirmado por la prueba de Jarque-Bera con un estadístico alto (613,624) y un p-valor de cero, rechazando fuertemente la hipótesis de normalidad. Esto implica que los residuos presentan una cantidad considerable de valores atípicos y no siguen una distribución normal.
La prueba de Dickey-Fuller muestra un estadístico de -16.53 con un p-valor extremadamente bajo (2.03e-29), indicando que los residuos son estacionarios, lo cual es positivo para la estabilidad del modelo en el tiempo. La prueba KPSS, con un estadístico de 0.57 y un p-valor cercano a 0.0259, sugiere una ligera tendencia no estacionaria, pero este resultado es menos fuerte que en la Dickey-Fuller, lo que mantiene la estación del modelo con alguna reserva.
test_predictions, test_targets, test_metrics_df = evaluate_and_create_row(mlp_model, test_loader, criterion, scaler, window_size, "MLP")
plt.plot(test_predictions.reshape(-1), label='test_predictions')
plt.plot(test_targets.reshape(-1), label='test_targets')
plt.legend()
plt.show()
test_metrics_df
| Model | MAPE | RMSE | R² | R² adj | |
|---|---|---|---|---|---|
| 0 | MLP | inf | 0.007715 | 0.955192 | 0.954149 |
La gráfica de los conjuntos de entrenamiento y validación muestra un ajuste notablemente preciso, donde las predicciones del modelo MLP siguen de cerca la tendencia y las fluctuaciones de los datos reales en ambas fases. Este buen ajuste sugiere que el modelo captura efectivamente los patrones y variaciones de la serie de tiempo de precios. Sin embargo, dado el comportamiento de los residuos observado en las métricas de error y pruebas de normalidad, es importante considerar posibles ajustes o regularizaciones adicionales para mejorar la capacidad general del modelo y manejar mejor la presencia de valores atípicos o colas pesadas.
import matplotlib.pyplot as plt
import pandas as pd
# Asegúrate de que 'lstm_result_df' contiene resultados de cada combinación de `window_size` y `train_steps`
# Añadimos una columna para numerar las corridas de entrenamiento
mlp_result_df['Run'] = range(1, len(lstm_result_df) + 1)
# Selecciona la métrica de los residuos que deseas graficar en el eje Y
# Aquí usaremos 'RMSE' como ejemplo; ajusta si prefieres otra métrica, como 'MAPE'
metric_to_plot = 'RMSE'
plt.figure(figsize=(10, 6))
plt.plot(lstm_result_df['Run'], lstm_result_df[metric_to_plot], marker='o', linestyle='-')
plt.xlabel("Run (Combination of Window Size and Training Step Size)")
plt.ylabel(f"{metric_to_plot} (Residual Score)")
plt.title(f"Error/Score vs Run for MLP Model")
plt.grid()
plt.show()
MLP Retornos acumulados#
window_sizes = [7, 14, 21, 28]
training_step_sizes = [7, 14, 21, 28]
validation_steps = 14
test_steps = 14
columns = ['Model', 'MAPE', 'RMSE', 'R²', 'R² adj']
mlp_result_df = pd.DataFrame(columns=columns)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'Device: {device}')
for window_size, train_steps in itertools.product(window_sizes, training_step_sizes):
print(f'Window Size: {window_size}, Training Step Size: {train_steps}')
model_name = f'mlp_modelAt_{window_size}_{train_steps}.pth'
if os.path.exists(os.path.join(shared_folder_path, model_name)):
checkpoint = torch.load(os.path.join(shared_folder_path, model_name))
valid_metrics_df = checkpoint['valid_metrics_df']
mlp_result_df = pd.concat([mlp_result_df, valid_metrics_df], ignore_index=True)
continue
Xtrains, ytrains, Xvalids, yvalids, Xtests, ytests = create_windows(btc_close_tensor, window_size, train_steps, validation_steps, test_steps)
Xtrains = torch.tensor(Xtrains, dtype=torch.float32).view(-1, train_steps, window_size).to(device)
ytrains = torch.tensor(ytrains, dtype=torch.float32).view(-1, train_steps, 1).to(device)
Xvalids = torch.tensor(Xvalids, dtype=torch.float32).view(-1, validation_steps, window_size).to(device)
yvalids = torch.tensor(yvalids, dtype=torch.float32).view(-1, validation_steps, 1).to(device)
Xtests = torch.tensor(Xtests, dtype=torch.float32).view(-1, test_steps, window_size).to(device)
ytests = torch.tensor(ytests, dtype=torch.float32).view(-1, test_steps, 1).to(device)
train_dataset = TensorDataset(Xtrains, ytrains)
valid_dataset = TensorDataset(Xvalids, yvalids)
test_dataset = TensorDataset(Xtests, ytests)
batch_size = 10
train_loader_original = DataLoader(train_dataset, batch_size=batch_size, shuffle=False)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
valid_loader = DataLoader(valid_dataset, batch_size=batch_size, shuffle=False)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
input_size = window_size # Each feature is a single value (univariate time series)
hidden_size = 5
output_size = 1
model = MLPModel(input_size, hidden_size, output_size).to(device)
num_epochs = 50
learning_rate = 0.001
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
for epoch in range(num_epochs):
model.train()
epoch_loss = 0
for X_batch, y_batch in train_loader:
optimizer.zero_grad() # Zero out the gradients for the next batch
outputs = model(X_batch)
loss = criterion(outputs, y_batch)
loss.backward() # Backpropagation
optimizer.step() # Optimize the weights
epoch_loss += loss.item()
if (epoch + 1) % 10 == 0:
avg_loss = epoch_loss / len(train_loader)
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {avg_loss:.4f}')
model_name = f'mlp_model_{window_size}_{train_steps}.pth'
_, _, valid_metrics_df = evaluate_and_create_row(model, valid_loader, criterion, scaler, window_size, model_name)
mlp_result_df = pd.concat([mlp_result_df, valid_metrics_df], ignore_index=True)
torch.save({
'input_size': input_size,
'hidden_size': hidden_size,
'output_size': output_size,
'num_layers': num_layers,
'test_loader': test_loader,
'valid_metrics_df': valid_metrics_df,
'state_dict': model.state_dict()
}, os.path.join(shared_folder_path, model_name))
Device: cpu
Window Size: 7, Training Step Size: 7
Epoch [10/50], Loss: 0.0493
Epoch [20/50], Loss: 0.0470
Epoch [30/50], Loss: 0.0526
Epoch [40/50], Loss: 0.0505
Epoch [50/50], Loss: 0.0527
Window Size: 7, Training Step Size: 14
Epoch [10/50], Loss: 0.0472
Epoch [20/50], Loss: 0.0339
Epoch [30/50], Loss: 0.0241
Epoch [40/50], Loss: 0.0176
Epoch [50/50], Loss: 0.0130
Window Size: 7, Training Step Size: 21
Epoch [10/50], Loss: 0.1649
Epoch [20/50], Loss: 0.0319
Epoch [30/50], Loss: 0.0002
Epoch [40/50], Loss: 0.0002
Epoch [50/50], Loss: 0.0002
Window Size: 7, Training Step Size: 28
Epoch [10/50], Loss: 0.0376
Epoch [20/50], Loss: 0.0082
Epoch [30/50], Loss: 0.0003
Epoch [40/50], Loss: 0.0002
Epoch [50/50], Loss: 0.0002
Window Size: 14, Training Step Size: 7
Epoch [10/50], Loss: 0.0010
Epoch [20/50], Loss: 0.0003
Epoch [30/50], Loss: 0.0003
Epoch [40/50], Loss: 0.0003
Epoch [50/50], Loss: 0.0003
Window Size: 14, Training Step Size: 14
Epoch [10/50], Loss: 0.0145
Epoch [20/50], Loss: 0.0005
Epoch [30/50], Loss: 0.0004
Epoch [40/50], Loss: 0.0005
Epoch [50/50], Loss: 0.0004
Window Size: 14, Training Step Size: 21
Epoch [10/50], Loss: 0.0210
Epoch [20/50], Loss: 0.0069
Epoch [30/50], Loss: 0.0006
Epoch [40/50], Loss: 0.0003
Epoch [50/50], Loss: 0.0003
Window Size: 14, Training Step Size: 28
Epoch [10/50], Loss: 0.0306
Epoch [20/50], Loss: 0.0019
Epoch [30/50], Loss: 0.0006
Epoch [40/50], Loss: 0.0005
Epoch [50/50], Loss: 0.0005
Window Size: 21, Training Step Size: 7
Epoch [10/50], Loss: 0.0007
Epoch [20/50], Loss: 0.0004
Epoch [30/50], Loss: 0.0004
Epoch [40/50], Loss: 0.0004
Epoch [50/50], Loss: 0.0003
Window Size: 21, Training Step Size: 14
Epoch [10/50], Loss: 0.0021
Epoch [20/50], Loss: 0.0006
Epoch [30/50], Loss: 0.0005
Epoch [40/50], Loss: 0.0005
Epoch [50/50], Loss: 0.0004
Window Size: 21, Training Step Size: 21
Epoch [10/50], Loss: 0.0954
Epoch [20/50], Loss: 0.0435
Epoch [30/50], Loss: 0.0195
Epoch [40/50], Loss: 0.0098
Epoch [50/50], Loss: 0.0060
Window Size: 21, Training Step Size: 28
Epoch [10/50], Loss: 0.0136
Epoch [20/50], Loss: 0.0008
Epoch [30/50], Loss: 0.0006
Epoch [40/50], Loss: 0.0006
Epoch [50/50], Loss: 0.0006
Window Size: 28, Training Step Size: 7
Epoch [10/50], Loss: 0.0031
Epoch [20/50], Loss: 0.0006
Epoch [30/50], Loss: 0.0005
Epoch [40/50], Loss: 0.0005
Epoch [50/50], Loss: 0.0005
Window Size: 28, Training Step Size: 14
Epoch [10/50], Loss: 0.0325
Epoch [20/50], Loss: 0.0098
Epoch [30/50], Loss: 0.0014
Epoch [40/50], Loss: 0.0008
Epoch [50/50], Loss: 0.0006
Window Size: 28, Training Step Size: 21
Epoch [10/50], Loss: 0.0432
Epoch [20/50], Loss: 0.0420
Epoch [30/50], Loss: 0.0420
Epoch [40/50], Loss: 0.0420
Epoch [50/50], Loss: 0.0420
Window Size: 28, Training Step Size: 28
Epoch [10/50], Loss: 0.0289
Epoch [20/50], Loss: 0.0176
Epoch [30/50], Loss: 0.0129
Epoch [40/50], Loss: 0.0100
Epoch [50/50], Loss: 0.0070
mlp_result_df
| Model | MAPE | RMSE | R² | R² adj | |
|---|---|---|---|---|---|
| 0 | mlp_model_7_7.pth | inf | 0.111540 | -0.000394 | -0.003931 |
| 1 | mlp_model_7_14.pth | inf | 0.052313 | 0.771780 | 0.770808 |
| 2 | mlp_model_7_21.pth | inf | 0.008427 | 0.993903 | 0.993873 |
| 3 | mlp_model_7_28.pth | inf | 0.008571 | 0.993451 | 0.993414 |
| 4 | mlp_model_14_7.pth | inf | 0.012035 | 0.987917 | 0.987831 |
| 5 | mlp_model_14_14.pth | inf | 0.011648 | 0.988487 | 0.988389 |
| 6 | mlp_model_14_21.pth | inf | 0.011625 | 0.988097 | 0.987978 |
| 7 | mlp_model_14_28.pth | inf | 0.010995 | 0.989175 | 0.989051 |
| 8 | mlp_model_21_7.pth | inf | 0.009064 | 0.992960 | 0.992884 |
| 9 | mlp_model_21_14.pth | inf | 0.010285 | 0.990722 | 0.990602 |
| 10 | mlp_model_21_21.pth | inf | 0.037986 | 0.875098 | 0.873214 |
| 11 | mlp_model_21_28.pth | inf | 0.015044 | 0.978761 | 0.978388 |
| 12 | mlp_model_28_7.pth | inf | 0.016274 | 0.976696 | 0.976361 |
| 13 | mlp_model_28_14.pth | inf | 0.011151 | 0.988966 | 0.988774 |
| 14 | mlp_model_28_21.pth | inf | 0.105297 | -0.000045 | -0.020469 |
| 15 | mlp_model_28_28.pth | inf | 0.045026 | 0.826381 | 0.822340 |
# Identify the model with the smallest R² adjusted value
model_with_min_r2_adj = mlp_result_df.loc[mlp_result_df['R² adj'].idxmax(), 'Model']
print(f"Model with the smallest R² adjusted: {model_with_min_r2_adj}")
checkpoint = torch.load(os.path.join(shared_folder_path, model_with_min_r2_adj))
input_size = checkpoint['input_size']
hidden_size = checkpoint['hidden_size']
output_size = checkpoint['output_size']
num_layers = checkpoint['num_layers']
test_loader = checkpoint['test_loader']
mlp_model = MLPModel(input_size, hidden_size, output_size)
mlp_model.load_state_dict(checkpoint['state_dict'])
mlp_model.to(device)
print("Model loaded successfully!")
residues, results_df = evaluate_model_residues(mlp_model, test_loader, criterion)
results_df
Model with the smallest R² adjusted: mlp_model_7_21.pth
Model loaded successfully!
<Figure size 1000x600 with 0 Axes>
<Figure size 1000x600 with 0 Axes>
| Value | |
|---|---|
| MAPE | 1.424688e+11 |
| RMSE | 1.752850e-02 |
| R2 | 9.928690e-01 |
| Kurtosis (statistic) | 1.790026e+01 |
| Kurtosis (p-value) | 1.173680e-71 |
| Skewness (statistic) | 1.879107e+01 |
| Skewness (p-value) | 8.935780e-79 |
| Jarque-Bera (statistic) | 1.434069e+04 |
| Jarque-Bera (p-value) | 0.000000e+00 |
| Dickey Fuller (statistic) | -6.559651e+00 |
| Dickey Fuller (p-value) | 8.443897e-09 |
| KPSS test (statistic) | 1.191223e-01 |
| KPSS test (p-value) | 1.000000e-01 |
Analisis graficas#
Al observar los gráficos de los residuos de la serie de tiempo de los retornos acumulados del Bitcoin con el modelo MLP, noto en la primera gráfica que la variación inicial de los residuos es considerablemente alta, pero esta fluctúa en menor medida conforme avanza el tiempo, llegando a ser mínima al final. Esto sugiere que el modelo logra mejorar su precisión a medida que se estabiliza.
El histograma de los residuos indica una distribución aparentemente normal, aunque la presencia de una alta concentración en el centro es notoria, lo cual podría estar indicando un exceso de valores cercanos a la media y una menor dispersión en general. Al observar el QQ plot, se aprecia que las desviaciones de la línea roja en las puntas sugieren colas pesadas o valores atípicos en los extremos, mientras que la línea azul muestra una forma sinusoidal, señal de una tendencia cíclica o patrones adicionales no captados por el modelo en ciertas áreas.
Finalmente, en la prueba ACF, cinco rezagos caen fuera de la banda de significancia en un patrón descendente. Esto sugiere que persiste una autocorrelación residual que el modelo no ha logrado captar completamente, aunque con una disminución progresiva que indica una mayor estabilidad en los residuos a medida que avanza la serie.
Analisis variables#
test_predictions, test_targets, test_metrics_df = evaluate_and_create_row(mlp_model, test_loader, criterion, scaler, window_size, "MLP")
test_predictions, test_targets, test_metrics_df = evaluate_and_create_row(mlp_model, test_loader, criterion, scaler, window_size, "MLP")
plt.plot(test_predictions.reshape(-1), label='test_predictions')
plt.plot(test_targets.reshape(-1), label='test_targets')
plt.legend()
plt.show()
test_metrics_df
| Model | MAPE | RMSE | R² | R² adj | |
|---|---|---|---|---|---|
| 0 | MLP | inf | 0.008998 | 0.992869 | 0.992725 |
La gráfica de la predicción y el conjunto de validación muestra un ajuste muy preciso, en el que las predicciones del modelo MLP siguen de cerca la serie de los datos reales, confirmando una gran capacidad de ajuste. Los valores cuantitativos también respaldan este desempeño: con un MAPE de 6.04%, un RMSE bajo de 0.0741, y un coeficiente de determinación R² de 0.9961 (y R² ajustado de 0.9960), el modelo demuestra alta precisión en la captura de los retornos acumulados del Bitcoin. Estos resultados indican que el modelo es eficaz en reflejar la dinámica de los retornos acumulados y en reducir los errores de predicción.
import matplotlib.pyplot as plt
import pandas as pd
# Asegúrate de que 'lstm_result_df' contiene resultados de cada combinación de `window_size` y `train_steps`
# Añadimos una columna para numerar las corridas de entrenamiento
mlp_result_df['Run'] = range(1, len(lstm_result_df) + 1)
# Selecciona la métrica de los residuos que deseas graficar en el eje Y
# Aquí usaremos 'RMSE' como ejemplo; ajusta si prefieres otra métrica, como 'MAPE'
metric_to_plot = 'RMSE'
plt.figure(figsize=(10, 6))
plt.plot(lstm_result_df['Run'], lstm_result_df[metric_to_plot], marker='o', linestyle='-')
plt.xlabel("Run (Combination of Window Size and Training Step Size)")
plt.ylabel(f"{metric_to_plot} (Residual Score)")
plt.title(f"Error/Score vs Run for MLP Model")
plt.grid()
plt.show()
MLP Volatilidad#
window_sizes = [7, 14, 21, 28]
training_step_sizes = [7, 14, 21, 28]
validation_steps = 14
test_steps = 14
columns = ['Model', 'MAPE', 'RMSE', 'R²', 'R² adj']
mlp_result_df = pd.DataFrame(columns=columns)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'Device: {device}')
for window_size, train_steps in itertools.product(window_sizes, training_step_sizes):
print(f'Window Size: {window_size}, Training Step Size: {train_steps}')
model_name = f'mlp_modelVol_{window_size}_{train_steps}.pth'
if os.path.exists(os.path.join(shared_folder_path, model_name)):
checkpoint = torch.load(os.path.join(shared_folder_path, model_name))
valid_metrics_df = checkpoint['valid_metrics_df']
mlp_result_dfAt = pd.concat([mlp_result_df, valid_metrics_df], ignore_index=True)
continue
Xtrains, ytrains, Xvalids, yvalids, Xtests, ytests = create_windows(btc_Vol_tensor, window_size, train_steps, validation_steps, test_steps)
Xtrains = torch.tensor(Xtrains, dtype=torch.float32).view(-1, train_steps, window_size).to(device)
ytrains = torch.tensor(ytrains, dtype=torch.float32).view(-1, train_steps, 1).to(device)
Xvalids = torch.tensor(Xvalids, dtype=torch.float32).view(-1, validation_steps, window_size).to(device)
yvalids = torch.tensor(yvalids, dtype=torch.float32).view(-1, validation_steps, 1).to(device)
Xtests = torch.tensor(Xtests, dtype=torch.float32).view(-1, test_steps, window_size).to(device)
ytests = torch.tensor(ytests, dtype=torch.float32).view(-1, test_steps, 1).to(device)
train_dataset = TensorDataset(Xtrains, ytrains)
valid_dataset = TensorDataset(Xvalids, yvalids)
test_dataset = TensorDataset(Xtests, ytests)
batch_size = 7
train_loader_original = DataLoader(train_dataset, batch_size=batch_size, shuffle=False)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
valid_loader = DataLoader(valid_dataset, batch_size=batch_size, shuffle=False)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
input_size = window_size # Each feature is a single value (univariate time series)
hidden_size = 50
output_size = 1
model = MLPModel(input_size, hidden_size, output_size).to(device)
num_epochs = 50
learning_rate = 0.01
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
for epoch in range(num_epochs):
model.train()
epoch_loss = 0
for X_batch, y_batch in train_loader:
optimizer.zero_grad() # Zero out the gradients for the next batch
outputs = model(X_batch)
loss = criterion(outputs, y_batch)
loss.backward() # Backpropagation
optimizer.step() # Optimize the weights
epoch_loss += loss.item()
if (epoch + 1) % 10 == 0:
avg_loss = epoch_loss / len(train_loader)
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {avg_loss:.4f}')
model_name = f'mlp_model_{window_size}_{train_steps}.pth'
_, _, valid_metrics_df = evaluate_and_create_row(model, valid_loader, criterion, scaler, window_size, model_name)
mlp_result_df = pd.concat([mlp_result_df, valid_metrics_df], ignore_index=True)
torch.save({
'input_size': input_size,
'hidden_size': hidden_size,
'output_size': output_size,
'num_layers': num_layers,
'test_loader': test_loader,
'valid_metrics_df': valid_metrics_df,
'state_dict': model.state_dict()
}, os.path.join(shared_folder_path, model_name))
Device: cpu
Window Size: 7, Training Step Size: 7
Epoch [10/50], Loss: nan
Epoch [20/50], Loss: nan
Epoch [30/50], Loss: nan
Epoch [40/50], Loss: nan
Epoch [50/50], Loss: nan
Window Size: 7, Training Step Size: 14
Epoch [10/50], Loss: nan
Epoch [20/50], Loss: nan
Epoch [30/50], Loss: nan
Epoch [40/50], Loss: nan
Epoch [50/50], Loss: nan
Window Size: 7, Training Step Size: 21
Epoch [10/50], Loss: 0.0004
Epoch [20/50], Loss: 0.0003
Epoch [30/50], Loss: 0.0003
Epoch [40/50], Loss: 0.0003
Epoch [50/50], Loss: 0.0003
Window Size: 7, Training Step Size: 28
Epoch [10/50], Loss: 0.0004
Epoch [20/50], Loss: 0.0020
Epoch [30/50], Loss: 0.0004
Epoch [40/50], Loss: 0.0004
Epoch [50/50], Loss: 0.0003
Window Size: 14, Training Step Size: 7
Epoch [10/50], Loss: nan
Epoch [20/50], Loss: nan
Epoch [30/50], Loss: nan
Epoch [40/50], Loss: nan
Epoch [50/50], Loss: nan
Window Size: 14, Training Step Size: 14
Epoch [10/50], Loss: 0.0019
Epoch [20/50], Loss: 0.0004
Epoch [30/50], Loss: 0.0004
Epoch [40/50], Loss: 0.0005
Epoch [50/50], Loss: 0.0030
Window Size: 14, Training Step Size: 21
Epoch [10/50], Loss: 0.0005
Epoch [20/50], Loss: 0.0004
Epoch [30/50], Loss: 0.0003
Epoch [40/50], Loss: 0.0004
Epoch [50/50], Loss: 0.0002
Window Size: 14, Training Step Size: 28
Epoch [10/50], Loss: 0.0005
Epoch [20/50], Loss: 0.0004
Epoch [30/50], Loss: 0.0004
Epoch [40/50], Loss: 0.0014
Epoch [50/50], Loss: 0.0003
Window Size: 21, Training Step Size: 7
Epoch [10/50], Loss: 0.0009
Epoch [20/50], Loss: 0.0005
Epoch [30/50], Loss: 0.0004
Epoch [40/50], Loss: 0.0004
Epoch [50/50], Loss: 0.0003
Window Size: 21, Training Step Size: 14
Epoch [10/50], Loss: 0.0005
Epoch [20/50], Loss: 0.0007
Epoch [30/50], Loss: 0.0004
Epoch [40/50], Loss: 0.0008
Epoch [50/50], Loss: 0.0004
Window Size: 21, Training Step Size: 21
Epoch [10/50], Loss: 0.0024
Epoch [20/50], Loss: 0.0006
Epoch [30/50], Loss: 0.0004
Epoch [40/50], Loss: 0.0005
Epoch [50/50], Loss: 0.0004
Window Size: 21, Training Step Size: 28
Epoch [10/50], Loss: 0.0013
Epoch [20/50], Loss: 0.0005
Epoch [30/50], Loss: 0.0005
Epoch [40/50], Loss: 0.0005
Epoch [50/50], Loss: 0.0008
Window Size: 28, Training Step Size: 7
Epoch [10/50], Loss: 0.0014
Epoch [20/50], Loss: 0.0040
Epoch [30/50], Loss: 0.0014
Epoch [40/50], Loss: 0.0003
Epoch [50/50], Loss: 0.0005
Window Size: 28, Training Step Size: 14
Epoch [10/50], Loss: 0.0005
Epoch [20/50], Loss: 0.0004
Epoch [30/50], Loss: 0.0006
Epoch [40/50], Loss: 0.0003
Epoch [50/50], Loss: 0.0003
Window Size: 28, Training Step Size: 21
Epoch [10/50], Loss: 0.0006
Epoch [20/50], Loss: 0.0006
Epoch [30/50], Loss: 0.0006
Epoch [40/50], Loss: 0.0004
Epoch [50/50], Loss: 0.0004
Window Size: 28, Training Step Size: 28
Epoch [10/50], Loss: 0.0005
Epoch [20/50], Loss: 0.0004
Epoch [30/50], Loss: 0.0003
Epoch [40/50], Loss: 0.0004
Epoch [50/50], Loss: 0.0003
mlp_result_df
| Model | MAPE | RMSE | R² | R² adj | |
|---|---|---|---|---|---|
| 0 | mlp_model_7_7.pth | NaN | NaN | NaN | NaN |
| 1 | mlp_model_7_14.pth | NaN | NaN | NaN | NaN |
| 2 | mlp_model_7_21.pth | inf | 0.008889 | 0.946548 | 0.946281 |
| 3 | mlp_model_7_28.pth | inf | 0.008839 | 0.940559 | 0.940219 |
| 4 | mlp_model_14_7.pth | NaN | NaN | NaN | NaN |
| 5 | mlp_model_14_14.pth | inf | 0.021289 | 0.711887 | 0.709423 |
| 6 | mlp_model_14_21.pth | inf | 0.009788 | 0.968124 | 0.967805 |
| 7 | mlp_model_14_28.pth | inf | 0.010102 | 0.932782 | 0.932009 |
| 8 | mlp_model_21_7.pth | inf | 0.009198 | 0.968017 | 0.967673 |
| 9 | mlp_model_21_14.pth | inf | 0.010734 | 0.934823 | 0.933983 |
| 10 | mlp_model_21_21.pth | inf | 0.010530 | 0.925374 | 0.924248 |
| 11 | mlp_model_21_28.pth | inf | 0.015617 | 0.788890 | 0.785183 |
| 12 | mlp_model_28_7.pth | inf | 0.014243 | 0.864365 | 0.862412 |
| 13 | mlp_model_28_14.pth | inf | 0.009434 | 0.963735 | 0.963104 |
| 14 | mlp_model_28_21.pth | inf | 0.010954 | 0.919970 | 0.918336 |
| 15 | mlp_model_28_28.pth | inf | 0.009818 | 0.966728 | 0.965953 |
# Identify the model with the smallest R² adjusted value
model_with_min_r2_adj = mlp_result_df.loc[mlp_result_df['R² adj'].idxmax(), 'Model']
print(f"Model with the smallest R² adjusted: {model_with_min_r2_adj}")
checkpoint = torch.load(os.path.join(shared_folder_path, model_with_min_r2_adj))
input_size = checkpoint['input_size']
hidden_size = checkpoint['hidden_size']
output_size = checkpoint['output_size']
num_layers = checkpoint['num_layers']
test_loader = checkpoint['test_loader']
mlp_model = MLPModel(input_size, hidden_size, output_size)
mlp_model.load_state_dict(checkpoint['state_dict'])
mlp_model.to(device)
print("Model loaded successfully!")
residues, results_df = evaluate_model_residues(mlp_model, test_loader, criterion)
results_df
Model with the smallest R² adjusted: mlp_model_14_21.pth
Model loaded successfully!
<Figure size 1000x600 with 0 Axes>
<Figure size 1000x600 with 0 Axes>
| Value | |
|---|---|
| MAPE | 1.920329e+12 |
| RMSE | 1.921109e-02 |
| R2 | 9.345505e-01 |
| Kurtosis (statistic) | 2.548781e+01 |
| Kurtosis (p-value) | 2.691248e-143 |
| Skewness (statistic) | -3.729165e+01 |
| Skewness (p-value) | 2.241610e-304 |
| Jarque-Bera (statistic) | 2.081523e+06 |
| Jarque-Bera (p-value) | 0.000000e+00 |
| Dickey Fuller (statistic) | -1.106435e+01 |
| Dickey Fuller (p-value) | 4.722548e-20 |
| KPSS test (statistic) | 3.028506e-02 |
| KPSS test (p-value) | 1.000000e-01 |
Analisis de los graficos#
Al observar los gráficos de los residuos de la predicción de la volatilidad del Bitcoin, noto que al inicio la variación en los residuos es baja y aumenta de forma gradual con el tiempo, pero esta variación nunca llega a ser muy pronunciada, lo que sugiere una relativa estabilidad en el error a lo largo del tiempo.
El histograma de los residuos indica una distribución cercana a la normal, aunque presenta una punta central muy pronunciada, lo que sugiere una alta frecuencia de errores alrededor de la media. En el QQ-plot, las desviaciones de la línea roja son leves en las puntas, señalando ligeras discrepancias en los extremos, pero en general, el ajuste no parece afectado significativamente por valores atípicos o grandes sesgos.
Finalmente, el ACF muestra solo dos rezagos pequeños por fuera de la banda de significancia y en descenso, lo cual sugiere que hay una baja autocorrelación remanente en los residuos, indicando que el modelo logra capturar la mayoría de las dependencias temporales en los datos.
Analisis residuos#
Al observar los gráficos de los residuos de la predicción de la volatilidad del Bitcoin, noto que al inicio la variación en los residuos es baja y aumenta de forma gradual con el tiempo, pero esta variación nunca llega a ser muy pronunciada, lo que sugiere una relativa estabilidad en el error a lo largo del tiempo.
El histograma de los residuos indica una distribución cercana a la normal, aunque presenta una punta central muy pronunciada, lo que sugiere una alta frecuencia de errores alrededor de la media. En el QQ-plot, las desviaciones de la línea roja son leves en las puntas, señalando ligeras discrepancias en los extremos, pero en general, el ajuste no parece afectado significativamente por valores atípicos o grandes sesgos.
Finalmente, el ACF muestra solo dos rezagos pequeños por fuera de la banda de significancia y en descenso, lo cual sugiere que hay una baja autocorrelación remanente en los residuos, indicando que el modelo logra capturar la mayoría de las dependencias temporales en los datos.
test_predictions, test_targets, test_metrics_df = evaluate_and_create_row(mlp_model, test_loader, criterion, scaler, window_size, "MLP")
plt.plot(test_predictions.reshape(-1), label='test_predictions')
plt.plot(test_targets.reshape(-1), label='test_targets')
plt.legend()
plt.show()
test_metrics_df
| Model | MAPE | RMSE | R² | R² adj | |
|---|---|---|---|---|---|
| 0 | MLP | inf | 0.009862 | 0.93455 | 0.933227 |
import matplotlib.pyplot as plt
import pandas as pd
# Asegúrate de que 'lstm_result_df' contiene resultados de cada combinación de `window_size` y `train_steps`
# Añadimos una columna para numerar las corridas de entrenamiento
mlp_result_df['Run'] = range(1, len(lstm_result_df) + 1)
# Selecciona la métrica de los residuos que deseas graficar en el eje Y
# Aquí usaremos 'RMSE' como ejemplo; ajusta si prefieres otra métrica, como 'MAPE'
metric_to_plot = 'RMSE'
plt.figure(figsize=(10, 6))
plt.plot(lstm_result_df['Run'], lstm_result_df[metric_to_plot], marker='o', linestyle='-')
plt.xlabel("Run (Combination of Window Size and Training Step Size)")
plt.ylabel(f"{metric_to_plot} (Residual Score)")
plt.title(f"Error/Score vs Run for MLP Model")
plt.grid()
plt.show()
RNN Precio#
import torch
import torch.nn as nn
import os
import itertools
import pandas as pd
from torch.utils.data import DataLoader, TensorDataset
def evaluate_and_create_row(model, loader, criterion, scaler, window_size, model_name):
model.eval()
all_predictions = []
all_targets = []
with torch.no_grad():
for X_batch, y_batch in loader:
outputs = model(X_batch)
all_predictions.append(outputs.cpu().numpy())
all_targets.append(y_batch.cpu().numpy())
# Aplanar las listas de predicciones y etiquetas
all_predictions = np.concatenate(all_predictions, axis=0).reshape(-1)
all_targets = np.concatenate(all_targets, axis=0).reshape(-1)
# Aseguramos que ambas listas tengan el mismo tamaño
min_length = min(len(all_targets), len(all_predictions))
all_targets = all_targets[:min_length]
all_predictions = all_predictions[:min_length]
# Calculamos las métricas
n = len(all_targets)
p = window_size
mape, rmse, r2, r2_adj = calculate_metrics(all_targets, all_predictions, n, p)
metrics_df = pd.DataFrame({
'Model': [model_name],
'MAPE': [mape],
'RMSE': [rmse],
'R²': [r2],
'R² adj': [r2_adj]
})
return all_predictions, all_targets, metrics_df
# Definimos la clase de la RNN
class RNNModel(nn.Module):
def __init__(self, input_size, hidden_size, output_size, num_layers=1):
super(RNNModel, self).__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
self.rnn = nn.RNN(input_size, hidden_size, num_layers, batch_first=True)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
out, _ = self.rnn(x, h0)
out = self.fc(out[:, -1, :]) # Tomamos solo la última salida
return out
# Configuración y parámetros
window_sizes = [7, 14, 21, 28]
training_step_sizes = [7, 14, 21, 28]
validation_steps = 14
test_steps = 14
columns = ['Model', 'MAPE', 'RMSE', 'R²', 'R² adj']
mlp_result_df = pd.DataFrame(columns=columns)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'Device: {device}')
for window_size, train_steps in itertools.product(window_sizes, training_step_sizes):
print(f'Window Size: {window_size}, Training Step Size: {train_steps}')
model_name = f'rnn_model_{window_size}_{train_steps}.pth'
if os.path.exists(os.path.join(shared_folder_path, model_name)):
checkpoint = torch.load(os.path.join(shared_folder_path, model_name))
valid_metrics_df = checkpoint['valid_metrics_df']
mlp_result_df = pd.concat([mlp_result_df, valid_metrics_df], ignore_index=True)
continue
# Preparamos las ventanas de datos
Xtrains, ytrains, Xvalids, yvalids, Xtests, ytests = create_windows(btc_close_tensor, window_size, train_steps, validation_steps, test_steps)
Xtrains = torch.tensor(Xtrains, dtype=torch.float32).view(-1, train_steps, window_size).to(device)
ytrains = torch.tensor(ytrains, dtype=torch.float32).view(-1, train_steps, 1).to(device)
Xvalids = torch.tensor(Xvalids, dtype=torch.float32).view(-1, validation_steps, window_size).to(device)
yvalids = torch.tensor(yvalids, dtype=torch.float32).view(-1, validation_steps, 1).to(device)
Xtests = torch.tensor(Xtests, dtype=torch.float32).view(-1, test_steps, window_size).to(device)
ytests = torch.tensor(ytests, dtype=torch.float32).view(-1, test_steps, 1).to(device)
# Configuración del DataLoader
batch_size = 1
train_loader = DataLoader(TensorDataset(Xtrains, ytrains), batch_size=batch_size, shuffle=True)
valid_loader = DataLoader(TensorDataset(Xvalids, yvalids), batch_size=batch_size, shuffle=False)
test_loader = DataLoader(TensorDataset(Xtests, ytests), batch_size=batch_size, shuffle=False)
# Parámetros de la RNN
input_size = window_size
hidden_size = 10
output_size = 1
num_layers = 1
# Definimos el modelo
model = RNNModel(input_size, hidden_size, output_size, num_layers).to(device)
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
# Entrenamiento de la RNN
num_epochs = 200
for epoch in range(num_epochs):
model.train()
epoch_loss = 0
for X_batch, y_batch in train_loader:
optimizer.zero_grad()
outputs = model(X_batch)
loss = criterion(outputs, y_batch)
loss.backward()
optimizer.step()
epoch_loss += loss.item()
if (epoch + 1) % 10 == 0:
avg_loss = epoch_loss / len(train_loader)
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {avg_loss:.4f}')
# Guardado del modelo
_, _, valid_metrics_df = evaluate_and_create_row(model, valid_loader, criterion, scaler, window_size, model_name)
mlp_result_df = pd.concat([mlp_result_df, valid_metrics_df], ignore_index=True)
torch.save({
'input_size': input_size,
'hidden_size': hidden_size,
'output_size': output_size,
'num_layers': num_layers,
'test_loader': test_loader,
'valid_metrics_df': valid_metrics_df,
'state_dict': model.state_dict()
}, os.path.join(shared_folder_path, model_name))
Device: cpu
Window Size: 7, Training Step Size: 7
Window Size: 7, Training Step Size: 14
Window Size: 7, Training Step Size: 21
Window Size: 7, Training Step Size: 28
Window Size: 14, Training Step Size: 7
Window Size: 14, Training Step Size: 14
Window Size: 14, Training Step Size: 21
Window Size: 14, Training Step Size: 28
Window Size: 21, Training Step Size: 7
Window Size: 21, Training Step Size: 14
Window Size: 21, Training Step Size: 21
Window Size: 21, Training Step Size: 28
Window Size: 28, Training Step Size: 7
Window Size: 28, Training Step Size: 14
Window Size: 28, Training Step Size: 21
Window Size: 28, Training Step Size: 28
def evaluate_model_residues(model, test_loader, criterion):
model.eval()
all_predictions = []
all_targets = []
with torch.no_grad():
for X_batch, y_batch in test_loader:
X_batch, y_batch = X_batch.to(device), y_batch.to(device)
outputs = model(X_batch)
all_predictions.append(outputs)
all_targets.append(y_batch)
# Concatenación y verificación de dimensiones
all_predictions = torch.cat(all_predictions, dim=0).cpu().numpy().reshape(-1)
all_targets = torch.cat(all_targets, dim=0).cpu().numpy().reshape(-1)
# Imprimir las longitudes para ver si coinciden
print(f"Dimensiones de all_predictions: {all_predictions.shape}")
print(f"Dimensiones de all_targets: {all_targets.shape}")
# Verificar si las dimensiones coinciden antes de realizar operaciones
if all_targets.shape != all_predictions.shape:
raise ValueError(f"Dimensiones no coinciden: all_targets {all_targets.shape}, all_predictions {all_predictions.shape}")
# Calcular residuos
residues = all_targets - all_predictions
# Calcular métricas
mape = mean_absolute_percentage_error(all_targets, all_predictions)
rmse = np.sqrt(mean_squared_error(all_targets, all_predictions))
r2 = r2_score(all_targets, all_predictions)
r2_adj = 1 - (1 - r2) * (len(all_targets) - 1) / (len(all_targets) - window_size - 1)
results_df = pd.DataFrame({
'MAPE': [mape],
'RMSE': [rmse],
'R²': [r2],
'R² adj': [r2_adj]
})
return residues, results_df
import os
import torch
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.metrics import mean_absolute_percentage_error, mean_squared_error, r2_score
from torch.utils.data import DataLoader
# Identificación del mejor modelo
model_with_max_r2_adj = mlp_result_df.loc[mlp_result_df['R² adj'].idxmax(), 'Model']
print(f"Model with the largest R² adjusted: {model_with_max_r2_adj}")
# Cargar el mejor modelo
checkpoint = torch.load(os.path.join(shared_folder_path, model_with_max_r2_adj))
input_size = checkpoint['input_size']
hidden_size = checkpoint['hidden_size']
output_size = checkpoint['output_size']
num_layers = checkpoint['num_layers']
test_loader = checkpoint['test_loader']
# Definir el modelo y cargar los parámetros
rnn_model = RNNModel(input_size, hidden_size, output_size, num_layers)
rnn_model.load_state_dict(checkpoint['state_dict'])
rnn_model.to(device)
print("Model loaded successfully!")
# Evaluación del modelo
def evaluate_model_residues(model, test_loader, criterion):
model.eval()
all_predictions = []
all_targets = []
with torch.no_grad():
for X_batch, y_batch in test_loader:
X_batch, y_batch = X_batch.to(device), y_batch.to(device)
outputs = model(X_batch)
all_predictions.append(outputs)
all_targets.append(y_batch)
# Concatenar todas las predicciones y objetivos
all_predictions = torch.cat(all_predictions, dim=0).cpu().numpy().reshape(-1)
all_targets = torch.cat(all_targets, dim=0).cpu().numpy().reshape(-1)
# Asegurar que las longitudes coincidan
min_length = min(len(all_targets), len(all_predictions))
all_targets = all_targets[:min_length]
all_predictions = all_predictions[:min_length]
# Calcular residuos
residues = all_targets - all_predictions
# Calcular métricas
mape = mean_absolute_percentage_error(all_targets, all_predictions)
rmse = np.sqrt(mean_squared_error(all_targets, all_predictions))
r2 = r2_score(all_targets, all_predictions)
r2_adj = 1 - (1 - r2) * (len(all_targets) - 1) / (len(all_targets) - input_size - 1)
results_df = pd.DataFrame({
'MAPE': [mape],
'RMSE': [rmse],
'R²': [r2],
'R² adj': [r2_adj]
})
return residues, results_df
# Evaluación y visualización
residues, results_df = evaluate_model_residues(rnn_model, test_loader, criterion)
test_predictions, test_targets, test_metrics_df = evaluate_and_create_row(rnn_model, test_loader, criterion, scaler, window_size, "RNN")
# Visualización de las predicciones y los valores reales
plt.plot(test_predictions.reshape(-1), label='Test Predictions')
plt.plot(test_targets.reshape(-1), label='Test Targets')
plt.legend()
plt.show()
# Mostrar el DataFrame con las métricas de evaluación
print(test_metrics_df)
Model with the largest R² adjusted: rnn_model_21_7.pth
Model loaded successfully!
Model MAPE RMSE R² R² adj
0 RNN 24.971811 0.120363 -0.886196 -1.357746
import torch
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm
from sklearn.metrics import mean_absolute_percentage_error, mean_squared_error, r2_score
from scipy.stats import kurtosistest, skewtest, jarque_bera
from statsmodels.tsa.stattools import adfuller, kpss
def count_parameters(model):
return sum(p.numel() for p in model.parameters() if p.requires_grad)
def calculate_adjusted_r2(all_targets, all_predictions, model):
n = len(all_targets)
k = count_parameters(model) # Total number of trainable parameters
r2 = r2_score(all_targets, all_predictions)
r2_adj = 1 - (1 - r2) * (n - 1) / (n - k - 1)
print(f"R²: {r2:.4f}")
print(f"R² adjusted: {r2_adj:.4f}")
print(f"Number of trainable parameters: {k}")
print(f"Number of measures: {n}")
return r2_adj
def evaluate_model_residues(model, data_loader, criterion):
model.eval()
all_predictions = []
all_targets = []
with torch.no_grad():
for X_batch, y_batch in data_loader:
outputs = model(X_batch)
all_predictions.append(outputs)
all_targets.append(y_batch)
# Concatenate all predictions and targets
all_predictions = torch.cat(all_predictions, dim=0).cpu().numpy().reshape(-1)
all_targets = torch.cat(all_targets, dim=0).cpu().numpy().reshape(-1)
# Asegurarse de que las longitudes coincidan
min_length = min(len(all_targets), len(all_predictions))
all_targets = all_targets[:min_length]
all_predictions = all_predictions[:min_length]
residues = all_targets - all_predictions
# Calculate statistics
mape = mean_absolute_percentage_error(all_targets, all_predictions)
rmse = np.sqrt(mean_squared_error(all_targets, all_predictions))
r2 = r2_score(all_targets, all_predictions)
kurt_stat, kurt_p_value = kurtosistest(residues)
skew_stat, skew_p_value = skewtest(residues)
jb_stat, jb_p_value = jarque_bera(residues)
adf_stat, adf_p_value, _, _, _, _ = adfuller(residues)
kpss_stat, kpss_p_value, _, _ = kpss(residues)
results = {
'MAPE': mape,
'RMSE': rmse,
'R²': r2,
'Kurtosis (statistic)': kurt_stat,
'Kurtosis (p-value)': kurt_p_value,
'Skewness (statistic)': skew_stat,
'Skewness (p-value)': skew_p_value,
'Jarque-Bera (statistic)': jb_stat,
'Jarque-Bera (p-value)': jb_p_value,
'Dickey Fuller (statistic)': adf_stat,
'Dickey Fuller (p-value)': adf_p_value,
'KPSS test (statistic)': kpss_stat,
'KPSS test (p-value)': kpss_p_value
}
results_df = pd.DataFrame.from_dict(results, orient='index', columns=['Value'])
# Plotting the time series of residues
plt.figure(figsize=(10, 6))
plt.plot(residues)
plt.title('Time Series of Residues')
plt.xlabel('Time')
plt.ylabel('Residues')
plt.show()
# Plotting the histogram of residues
plt.figure(figsize=(10, 6))
sns.histplot(residues, kde=True)
plt.title('Histogram of Residues')
plt.xlabel('Residues')
plt.ylabel('Frequency')
plt.show()
# Q-Q plot of residues
plt.figure(figsize=(10, 6))
sm.qqplot(residues, line='s')
plt.title('Q-Q Plot of Residues')
plt.show()
# ACF plot of residues
plt.figure(figsize=(10, 6))
sm.graphics.tsa.plot_acf(residues, lags=40)
plt.title('Autocorrelation Function (ACF) of Residues')
plt.show()
return residues, results_df
# Uso de la función para evaluar el modelo RNN
residues, results_df = evaluate_model_residues(rnn_model, test_loader, criterion)
print(results_df)
# Calcular el R² ajustado
adjusted_r2 = calculate_adjusted_r2(test_targets, test_predictions, rnn_model)
<Figure size 1000x600 with 0 Axes>
<Figure size 1000x600 with 0 Axes>
Value
MAPE 0.249718
RMSE 0.120363
R² -0.886196
Kurtosis (statistic) 1.452483
Kurtosis (p-value) 0.146367
Skewness (statistic) -3.511924
Skewness (p-value) 0.000445
Jarque-Bera (statistic) 15.616594
Jarque-Bera (p-value) 0.000406
Dickey Fuller (statistic) -2.615550
Dickey Fuller (p-value) 0.089826
KPSS test (statistic) 0.626704
KPSS test (p-value) 0.020209
R²: -0.8862
R² adjusted: 2.3138
Number of trainable parameters: 341
Number of measures: 141
import matplotlib.pyplot as plt
import pandas as pd
# Asegúrate de que 'lstm_result_df' contiene resultados de cada combinación de `window_size` y `train_steps`
# Añadimos una columna para numerar las corridas de entrenamiento
rnn_result_df['Run'] = range(1, len(lstm_result_df) + 1)
# Selecciona la métrica de los residuos que deseas graficar en el eje Y
# Aquí usaremos 'RMSE' como ejemplo; ajusta si prefieres otra métrica, como 'MAPE'
metric_to_plot = 'RMSE'
plt.figure(figsize=(10, 6))
plt.plot(lstm_result_df['Run'], lstm_result_df[metric_to_plot], marker='o', linestyle='-')
plt.xlabel("Run (Combination of Window Size and Training Step Size)")
plt.ylabel(f"{metric_to_plot} (Residual Score)")
plt.title(f"Error/Score vs Run for RNN Model")
plt.grid()
plt.show()
RNN At#
import torch
import torch.nn as nn
import os
import itertools
import pandas as pd
from torch.utils.data import DataLoader, TensorDataset
shared_folder_path = "C:\\Users\\Maestriadatos\\Documents\\lizcanollerenaparcial\\docs"
files = os.listdir(shared_folder_path)
print(files)
def evaluate_and_create_row(model, loader, criterion, scaler, window_size, model_name):
model.eval()
all_predictions = []
all_targets = []
with torch.no_grad():
for X_batch, y_batch in loader:
outputs = model(X_batch)
all_predictions.append(outputs.cpu().numpy())
all_targets.append(y_batch.cpu().numpy())
# Aplanar las listas de predicciones y etiquetas
all_predictions = np.concatenate(all_predictions, axis=0).reshape(-1)
all_targets = np.concatenate(all_targets, axis=0).reshape(-1)
# Aseguramos que ambas listas tengan el mismo tamaño
min_length = min(len(all_targets), len(all_predictions))
all_targets = all_targets[:min_length]
all_predictions = all_predictions[:min_length]
# Calculamos las métricas
n = len(all_targets)
p = window_size
mape, rmse, r2, r2_adj = calculate_metrics(all_targets, all_predictions, n, p)
metrics_df = pd.DataFrame({
'Model': [model_name],
'MAPE': [mape],
'RMSE': [rmse],
'R²': [r2],
'R² adj': [r2_adj]
})
return all_predictions, all_targets, metrics_df
# Definimos la clase de la RNN
class RNNModel(nn.Module):
def __init__(self, input_size, hidden_size, output_size, num_layers=1):
super(RNNModel, self).__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
self.rnn = nn.RNN(input_size, hidden_size, num_layers, batch_first=True)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
out, _ = self.rnn(x, h0)
out = self.fc(out[:, -1, :]) # Tomamos solo la última salida
return out
# Configuración y parámetros
window_sizes = [7, 14, 21, 28]
training_step_sizes = [7, 14, 21, 28]
validation_steps = 14
test_steps = 14
columns = ['Model', 'MAPE', 'RMSE', 'R²', 'R² adj']
rnn_result_df = pd.DataFrame(columns=columns)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'Device: {device}')
for window_size, train_steps in itertools.product(window_sizes, training_step_sizes):
print(f'Window Size: {window_size}, Training Step Size: {train_steps}')
model_name = f'rnn_modelAt_{window_size}_{train_steps}.pth'
if os.path.exists(os.path.join(shared_folder_path, model_name)):
checkpoint = torch.load(os.path.join(shared_folder_path, model_name))
valid_metrics_df = checkpoint['valid_metrics_df']
rnn_result_df = pd.concat([rnn_result_df, valid_metrics_df], ignore_index=True)
continue
# Preparamos las ventanas de datos
Xtrains, ytrains, Xvalids, yvalids, Xtests, ytests = create_windows(btc_At_tensor, window_size, train_steps, validation_steps, test_steps)
Xtrains = torch.tensor(Xtrains, dtype=torch.float32).view(-1, train_steps, window_size).to(device)
ytrains = torch.tensor(ytrains, dtype=torch.float32).view(-1, train_steps, 1).to(device)
Xvalids = torch.tensor(Xvalids, dtype=torch.float32).view(-1, validation_steps, window_size).to(device)
yvalids = torch.tensor(yvalids, dtype=torch.float32).view(-1, validation_steps, 1).to(device)
Xtests = torch.tensor(Xtests, dtype=torch.float32).view(-1, test_steps, window_size).to(device)
ytests = torch.tensor(ytests, dtype=torch.float32).view(-1, test_steps, 1).to(device)
# Configuración del DataLoader
batch_size = 1
train_loader = DataLoader(TensorDataset(Xtrains, ytrains), batch_size=batch_size, shuffle=True)
valid_loader = DataLoader(TensorDataset(Xvalids, yvalids), batch_size=batch_size, shuffle=False)
test_loader = DataLoader(TensorDataset(Xtests, ytests), batch_size=batch_size, shuffle=False)
# Parámetros de la RNN
input_size = window_size
hidden_size = 10
output_size = 1
num_layers = 1
# Definimos el modelo
model = RNNModel(input_size, hidden_size, output_size, num_layers).to(device)
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
# Entrenamiento de la RNN
num_epochs = 100
for epoch in range(num_epochs):
model.train()
epoch_loss = 0
for X_batch, y_batch in train_loader:
optimizer.zero_grad()
outputs = model(X_batch)
loss = criterion(outputs, y_batch)
loss.backward()
optimizer.step()
epoch_loss += loss.item()
if (epoch + 1) % 10 == 0:
avg_loss = epoch_loss / len(train_loader)
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {avg_loss:.4f}')
# Guardado del modelo
_, _, valid_metrics_df = evaluate_and_create_row(model, valid_loader, criterion, scaler, window_size, model_name)
rnn_result_df = pd.concat([rnn_result_df, valid_metrics_df], ignore_index=True)
torch.save({
'input_size': input_size,
'hidden_size': hidden_size,
'output_size': output_size,
'num_layers': num_layers,
'test_loader': test_loader,
'valid_metrics_df': valid_metrics_df,
'state_dict': model.state_dict()
}, os.path.join(shared_folder_path, model_name))
['.git', 'bitcoinhistoricaldata.csv', 'intro.md', 'logo.png', 'lstm_modelAt_14_14.pth', 'lstm_modelAt_14_21.pth', 'lstm_modelAt_14_28.pth', 'lstm_modelAt_14_7.pth', 'lstm_modelAt_21_14.pth', 'lstm_modelAt_21_21.pth', 'lstm_modelAt_21_28.pth', 'lstm_modelAt_21_7.pth', 'lstm_modelAt_28_14.pth', 'lstm_modelAt_28_21.pth', 'lstm_modelAt_28_28.pth', 'lstm_modelAt_28_7.pth', 'lstm_modelAt_7_14.pth', 'lstm_modelAt_7_21.pth', 'lstm_modelAt_7_28.pth', 'lstm_modelAt_7_7.pth', 'lstm_modelVol7_14_14.pth', 'lstm_modelVol7_14_21.pth', 'lstm_modelVol7_14_28.pth', 'lstm_modelVol7_14_7.pth', 'lstm_modelVol7_21_14.pth', 'lstm_modelVol7_21_21.pth', 'lstm_modelVol7_21_28.pth', 'lstm_modelVol7_21_7.pth', 'lstm_modelVol7_28_14.pth', 'lstm_modelVol7_28_21.pth', 'lstm_modelVol7_28_28.pth', 'lstm_modelVol7_28_7.pth', 'lstm_modelVol7_7_14.pth', 'lstm_modelVol7_7_21.pth', 'lstm_modelVol7_7_28.pth', 'lstm_modelVol7_7_7.pth', 'lstm_model_14_14.pth', 'lstm_model_14_21.pth', 'lstm_model_14_28.pth', 'lstm_model_14_7.pth', 'lstm_model_21_14.pth', 'lstm_model_21_21.pth', 'lstm_model_21_28.pth', 'lstm_model_21_7.pth', 'lstm_model_28_14.pth', 'lstm_model_28_21.pth', 'lstm_model_28_28.pth', 'lstm_model_28_7.pth', 'lstm_model_7_14.pth', 'lstm_model_7_21.pth', 'lstm_model_7_28.pth', 'lstm_model_7_7.pth', 'markdown-notebooks.md', 'markdown.md', 'mlp_model_14_14.pth', 'mlp_model_14_21.pth', 'mlp_model_14_28.pth', 'mlp_model_14_7.pth', 'mlp_model_21_14.pth', 'mlp_model_21_21.pth', 'mlp_model_21_28.pth', 'mlp_model_21_7.pth', 'mlp_model_28_14.pth', 'mlp_model_28_21.pth', 'mlp_model_28_28.pth', 'mlp_model_28_7.pth', 'mlp_model_7_14.pth', 'mlp_model_7_21.pth', 'mlp_model_7_28.pth', 'mlp_model_7_7.pth', 'notebooks.ipynb', 'references.bib', 'requirements.txt', 'rnn_modelAt_14_14.pth', 'rnn_modelAt_14_21.pth', 'rnn_modelAt_14_28.pth', 'rnn_modelAt_14_7.pth', 'rnn_modelAt_21_14.pth', 'rnn_modelAt_21_21.pth', 'rnn_modelAt_21_28.pth', 'rnn_modelAt_21_7.pth', 'rnn_modelAt_28_14.pth', 'rnn_modelAt_28_21.pth', 'rnn_modelAt_28_28.pth', 'rnn_modelAt_28_7.pth', 'rnn_modelAt_7_14.pth', 'rnn_modelAt_7_21.pth', 'rnn_modelAt_7_28.pth', 'rnn_modelAt_7_7.pth', 'rnn_modelVol_14_14.pth', 'rnn_modelVol_14_21.pth', 'rnn_modelVol_14_28.pth', 'rnn_modelVol_14_7.pth', 'rnn_modelVol_21_14.pth', 'rnn_modelVol_21_21.pth', 'rnn_modelVol_21_28.pth', 'rnn_modelVol_21_7.pth', 'rnn_modelVol_28_14.pth', 'rnn_modelVol_28_21.pth', 'rnn_modelVol_28_28.pth', 'rnn_modelVol_28_7.pth', 'rnn_modelVol_7_14.pth', 'rnn_modelVol_7_21.pth', 'rnn_modelVol_7_28.pth', 'rnn_modelVol_7_7.pth', 'rnn_model_14_14.pth', 'rnn_model_14_21.pth', 'rnn_model_14_28.pth', 'rnn_model_14_7.pth', 'rnn_model_21_14.pth', 'rnn_model_21_21.pth', 'rnn_model_21_28.pth', 'rnn_model_21_7.pth', 'rnn_model_28_14.pth', 'rnn_model_28_21.pth', 'rnn_model_28_28.pth', 'rnn_model_28_7.pth', 'rnn_model_7_14.pth', 'rnn_model_7_21.pth', 'rnn_model_7_28.pth', 'rnn_model_7_7.pth', '_build', '_config.yml', '_toc.yml']
Device: cpu
Window Size: 7, Training Step Size: 7
Window Size: 7, Training Step Size: 14
Window Size: 7, Training Step Size: 21
Window Size: 7, Training Step Size: 28
Window Size: 14, Training Step Size: 7
Window Size: 14, Training Step Size: 14
Window Size: 14, Training Step Size: 21
Window Size: 14, Training Step Size: 28
Window Size: 21, Training Step Size: 7
Window Size: 21, Training Step Size: 14
Window Size: 21, Training Step Size: 21
Window Size: 21, Training Step Size: 28
Window Size: 28, Training Step Size: 7
Window Size: 28, Training Step Size: 14
Window Size: 28, Training Step Size: 21
Window Size: 28, Training Step Size: 28
def evaluate_model_residues(model, test_loader, criterion):
model.eval()
all_predictions = []
all_targets = []
with torch.no_grad():
for X_batch, y_batch in test_loader:
X_batch, y_batch = X_batch.to(device), y_batch.to(device)
outputs = model(X_batch)
all_predictions.append(outputs)
all_targets.append(y_batch)
# Concatenación y verificación de dimensiones
all_predictions = torch.cat(all_predictions, dim=0).cpu().numpy().reshape(-1)
all_targets = torch.cat(all_targets, dim=0).cpu().numpy().reshape(-1)
# Imprimir las longitudes para ver si coinciden
print(f"Dimensiones de all_predictions: {all_predictions.shape}")
print(f"Dimensiones de all_targets: {all_targets.shape}")
# Verificar si las dimensiones coinciden antes de realizar operaciones
if all_targets.shape != all_predictions.shape:
raise ValueError(f"Dimensiones no coinciden: all_targets {all_targets.shape}, all_predictions {all_predictions.shape}")
# Calcular residuos
residues = all_targets - all_predictions
# Calcular métricas
mape = mean_absolute_percentage_error(all_targets, all_predictions)
rmse = np.sqrt(mean_squared_error(all_targets, all_predictions))
r2 = r2_score(all_targets, all_predictions)
r2_adj = 1 - (1 - r2) * (len(all_targets) - 1) / (len(all_targets) - window_size - 1)
results_df = pd.DataFrame({
'MAPE': [mape],
'RMSE': [rmse],
'R²': [r2],
'R² adj': [r2_adj]
})
return residues, results_df
import os
import torch
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.metrics import mean_absolute_percentage_error, mean_squared_error, r2_score
from torch.utils.data import DataLoader
mlp_result_df_filtered = mlp_result_df.dropna(subset=['R² adj'])
if mlp_result_df_filtered.empty:
raise ValueError("No hay modelos con valores válidos de 'R² adj'.")
else:
model_with_max_r2_adj = mlp_result_df_filtered.loc[mlp_result_df_filtered['R² adj'].idxmax(), 'Model']
print(f"Model with the largest R² adjusted: {model_with_max_r2_adj}")
# Cargar el mejor modelo
checkpoint = torch.load(os.path.join(shared_folder_path, model_with_max_r2_adj))
input_size = checkpoint['input_size']
hidden_size = checkpoint['hidden_size']
output_size = checkpoint['output_size']
num_layers = checkpoint['num_layers']
test_loader = checkpoint['test_loader']
# Definir el modelo y cargar los parámetros
rnn_model = RNNModel(input_size, hidden_size, output_size, num_layers)
rnn_model.load_state_dict(checkpoint['state_dict'])
rnn_model.to(device)
print("Model loaded successfully!")
# Evaluación del modelo
def evaluate_model_residues(model, test_loader, criterion):
model.eval()
all_predictions = []
all_targets = []
with torch.no_grad():
for X_batch, y_batch in test_loader:
X_batch, y_batch = X_batch.to(device), y_batch.to(device)
outputs = model(X_batch)
all_predictions.append(outputs)
all_targets.append(y_batch)
# Concatenar todas las predicciones y objetivos
all_predictions = torch.cat(all_predictions, dim=0).cpu().numpy().reshape(-1)
all_targets = torch.cat(all_targets, dim=0).cpu().numpy().reshape(-1)
# Asegurar que las longitudes coincidan
min_length = min(len(all_targets), len(all_predictions))
all_targets = all_targets[:min_length]
all_predictions = all_predictions[:min_length]
# Calcular residuos
residues = all_targets - all_predictions
# Calcular métricas
mape = mean_absolute_percentage_error(all_targets, all_predictions)
rmse = np.sqrt(mean_squared_error(all_targets, all_predictions))
r2 = r2_score(all_targets, all_predictions)
r2_adj = 1 - (1 - r2) * (len(all_targets) - 1) / (len(all_targets) - input_size - 1)
results_df = pd.DataFrame({
'MAPE': [mape],
'RMSE': [rmse],
'R²': [r2],
'R² adj': [r2_adj]
})
return residues, results_df
# Evaluación y visualización
residues, results_df = evaluate_model_residues(rnn_model, test_loader, criterion)
test_predictions, test_targets, test_metrics_df = evaluate_and_create_row(rnn_model, test_loader, criterion, scaler, window_size, "RNN")
# Visualización de las predicciones y los valores reales
plt.plot(test_predictions.reshape(-1), label='Test Predictions')
plt.plot(test_targets.reshape(-1), label='Test Targets')
plt.legend()
plt.show()
# Mostrar el DataFrame con las métricas de evaluación
print(test_metrics_df)
Model with the largest R² adjusted: rnn_model_21_7.pth
Model loaded successfully!
Model MAPE RMSE R² R² adj
0 RNN 24.971811 0.120363 -0.886196 -1.357746
import torch
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm
from sklearn.metrics import mean_absolute_percentage_error, mean_squared_error, r2_score
from scipy.stats import kurtosistest, skewtest, jarque_bera
from statsmodels.tsa.stattools import adfuller, kpss
def count_parameters(model):
return sum(p.numel() for p in model.parameters() if p.requires_grad)
def calculate_adjusted_r2(all_targets, all_predictions, model):
n = len(all_targets)
k = count_parameters(model) # Total number of trainable parameters
r2 = r2_score(all_targets, all_predictions)
r2_adj = 1 - (1 - r2) * (n - 1) / (n - k - 1)
print(f"R²: {r2:.4f}")
print(f"R² adjusted: {r2_adj:.4f}")
print(f"Number of trainable parameters: {k}")
print(f"Number of measures: {n}")
return r2_adj
def evaluate_model_residues(model, data_loader, criterion):
model.eval()
all_predictions = []
all_targets = []
with torch.no_grad():
for X_batch, y_batch in data_loader:
outputs = model(X_batch)
all_predictions.append(outputs)
all_targets.append(y_batch)
# Concatenate all predictions and targets
all_predictions = torch.cat(all_predictions, dim=0).cpu().numpy().reshape(-1)
all_targets = torch.cat(all_targets, dim=0).cpu().numpy().reshape(-1)
# Asegurarse de que las longitudes coincidan
min_length = min(len(all_targets), len(all_predictions))
all_targets = all_targets[:min_length]
all_predictions = all_predictions[:min_length]
residues = all_targets - all_predictions
# Calculate statistics
mape = mean_absolute_percentage_error(all_targets, all_predictions)
rmse = np.sqrt(mean_squared_error(all_targets, all_predictions))
r2 = r2_score(all_targets, all_predictions)
kurt_stat, kurt_p_value = kurtosistest(residues)
skew_stat, skew_p_value = skewtest(residues)
jb_stat, jb_p_value = jarque_bera(residues)
adf_stat, adf_p_value, _, _, _, _ = adfuller(residues)
kpss_stat, kpss_p_value, _, _ = kpss(residues)
results = {
'MAPE': mape,
'RMSE': rmse,
'R²': r2,
'Kurtosis (statistic)': kurt_stat,
'Kurtosis (p-value)': kurt_p_value,
'Skewness (statistic)': skew_stat,
'Skewness (p-value)': skew_p_value,
'Jarque-Bera (statistic)': jb_stat,
'Jarque-Bera (p-value)': jb_p_value,
'Dickey Fuller (statistic)': adf_stat,
'Dickey Fuller (p-value)': adf_p_value,
'KPSS test (statistic)': kpss_stat,
'KPSS test (p-value)': kpss_p_value
}
results_df = pd.DataFrame.from_dict(results, orient='index', columns=['Value'])
# Plotting the time series of residues
plt.figure(figsize=(10, 6))
plt.plot(residues)
plt.title('Time Series of Residues')
plt.xlabel('Time')
plt.ylabel('Residues')
plt.show()
# Plotting the histogram of residues
plt.figure(figsize=(10, 6))
sns.histplot(residues, kde=True)
plt.title('Histogram of Residues')
plt.xlabel('Residues')
plt.ylabel('Frequency')
plt.show()
# Q-Q plot of residues
plt.figure(figsize=(10, 6))
sm.qqplot(residues, line='s')
plt.title('Q-Q Plot of Residues')
plt.show()
# ACF plot of residues
plt.figure(figsize=(10, 6))
sm.graphics.tsa.plot_acf(residues, lags=40)
plt.title('Autocorrelation Function (ACF) of Residues')
plt.show()
return residues, results_df
# Uso de la función para evaluar el modelo RNN
residues, results_df = evaluate_model_residues(rnn_model, test_loader, criterion)
print(results_df)
# Calcular el R² ajustado
adjusted_r2 = calculate_adjusted_r2(test_targets, test_predictions, rnn_model)
<Figure size 1000x600 with 0 Axes>
<Figure size 1000x600 with 0 Axes>
Value
MAPE 0.249718
RMSE 0.120363
R² -0.886196
Kurtosis (statistic) 1.452483
Kurtosis (p-value) 0.146367
Skewness (statistic) -3.511924
Skewness (p-value) 0.000445
Jarque-Bera (statistic) 15.616594
Jarque-Bera (p-value) 0.000406
Dickey Fuller (statistic) -2.615550
Dickey Fuller (p-value) 0.089826
KPSS test (statistic) 0.626704
KPSS test (p-value) 0.020209
R²: -0.8862
R² adjusted: 2.3138
Number of trainable parameters: 341
Number of measures: 141
import matplotlib.pyplot as plt
import pandas as pd
# Asegúrate de que 'lstm_result_df' contiene resultados de cada combinación de `window_size` y `train_steps`
# Añadimos una columna para numerar las corridas de entrenamiento
lstm_result_df['Run'] = range(1, len(lstm_result_df) + 1)
# Selecciona la métrica de los residuos que deseas graficar en el eje Y
# Aquí usaremos 'RMSE' como ejemplo; ajusta si prefieres otra métrica, como 'MAPE'
metric_to_plot = 'RMSE'
plt.figure(figsize=(10, 6))
plt.plot(lstm_result_df['Run'], lstm_result_df[metric_to_plot], marker='o', linestyle='-')
plt.xlabel("Run (Combination of Window Size and Training Step Size)")
plt.ylabel(f"{metric_to_plot} (Residual Score)")
plt.title(f"Error/Score vs Run for RNN Model")
plt.grid()
plt.show()
RNN Volatilidad#
import torch
import torch.nn as nn
import os
import itertools
import pandas as pd
from torch.utils.data import DataLoader, TensorDataset
shared_folder_path = "C:\\Users\\Maestriadatos\\Documents\\lizcanollerenaparcial\\docs"
files = os.listdir(shared_folder_path)
print(files)
def evaluate_and_create_row(model, loader, criterion, scaler, window_size, model_name):
model.eval()
all_predictions = []
all_targets = []
with torch.no_grad():
for X_batch, y_batch in loader:
outputs = model(X_batch)
all_predictions.append(outputs.cpu().numpy())
all_targets.append(y_batch.cpu().numpy())
# Aplanar las listas de predicciones y etiquetas
all_predictions = np.concatenate(all_predictions, axis=0).reshape(-1)
all_targets = np.concatenate(all_targets, axis=0).reshape(-1)
# Aseguramos que ambas listas tengan el mismo tamaño
min_length = min(len(all_targets), len(all_predictions))
all_targets = all_targets[:min_length]
all_predictions = all_predictions[:min_length]
# Calculamos las métricas
n = len(all_targets)
p = window_size
mape, rmse, r2, r2_adj = calculate_metrics(all_targets, all_predictions, n, p)
metrics_df = pd.DataFrame({
'Model': [model_name],
'MAPE': [mape],
'RMSE': [rmse],
'R²': [r2],
'R² adj': [r2_adj]
})
return all_predictions, all_targets, metrics_df
# Definimos la clase de la RNN
class RNNModel(nn.Module):
def __init__(self, input_size, hidden_size, output_size, num_layers=1):
super(RNNModel, self).__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
self.rnn = nn.RNN(input_size, hidden_size, num_layers, batch_first=True)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
out, _ = self.rnn(x, h0)
out = self.fc(out[:, -1, :]) # Tomamos solo la última salida
return out
# Configuración y parámetros
window_sizes = [7, 14, 21, 28]
training_step_sizes = [7, 14, 21, 28]
validation_steps = 14
test_steps = 14
columns = ['Model', 'MAPE', 'RMSE', 'R²', 'R² adj']
rnn_result_df = pd.DataFrame(columns=columns)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'Device: {device}')
for window_size, train_steps in itertools.product(window_sizes, training_step_sizes):
print(f'Window Size: {window_size}, Training Step Size: {train_steps}')
model_name = f'rnn_modelVol_{window_size}_{train_steps}.pth'
if os.path.exists(os.path.join(shared_folder_path, model_name)):
checkpoint = torch.load(os.path.join(shared_folder_path, model_name))
valid_metrics_df = checkpoint['valid_metrics_df']
rnn_result_df = pd.concat([rnn_result_df, valid_metrics_df], ignore_index=True)
continue
# Preparamos las ventanas de datos
Xtrains, ytrains, Xvalids, yvalids, Xtests, ytests = create_windows(btc_Vol_tensor, window_size, train_steps, validation_steps, test_steps)
Xtrains = torch.tensor(Xtrains, dtype=torch.float32).view(-1, train_steps, window_size).to(device)
ytrains = torch.tensor(ytrains, dtype=torch.float32).view(-1, train_steps, 1).to(device)
Xvalids = torch.tensor(Xvalids, dtype=torch.float32).view(-1, validation_steps, window_size).to(device)
yvalids = torch.tensor(yvalids, dtype=torch.float32).view(-1, validation_steps, 1).to(device)
Xtests = torch.tensor(Xtests, dtype=torch.float32).view(-1, test_steps, window_size).to(device)
ytests = torch.tensor(ytests, dtype=torch.float32).view(-1, test_steps, 1).to(device)
# Configuración del DataLoader
batch_size = 1
train_loader = DataLoader(TensorDataset(Xtrains, ytrains), batch_size=batch_size, shuffle=True)
valid_loader = DataLoader(TensorDataset(Xvalids, yvalids), batch_size=batch_size, shuffle=False)
test_loader = DataLoader(TensorDataset(Xtests, ytests), batch_size=batch_size, shuffle=False)
# Parámetros de la RNN
input_size = window_size
hidden_size = 10
output_size = 1
num_layers = 1
# Definimos el modelo
model = RNNModel(input_size, hidden_size, output_size, num_layers).to(device)
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
# Entrenamiento de la RNN
num_epochs = 50
for epoch in range(num_epochs):
model.train()
epoch_loss = 0
for X_batch, y_batch in train_loader:
optimizer.zero_grad()
outputs = model(X_batch)
loss = criterion(outputs, y_batch)
loss.backward()
optimizer.step()
epoch_loss += loss.item()
if (epoch + 1) % 10 == 0:
avg_loss = epoch_loss / len(train_loader)
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {avg_loss:.4f}')
# Guardado del modelo
_, _, valid_metrics_df = evaluate_and_create_row(model, valid_loader, criterion, scaler, window_size, model_name)
rnn_result_df = pd.concat([rnn_result_df, valid_metrics_df], ignore_index=True)
torch.save({
'input_size': input_size,
'hidden_size': hidden_size,
'output_size': output_size,
'num_layers': num_layers,
'test_loader': test_loader,
'valid_metrics_df': valid_metrics_df,
'state_dict': model.state_dict()
}, os.path.join(shared_folder_path, model_name))
['.git', 'bitcoinhistoricaldata.csv', 'intro.md', 'logo.png', 'lstm_modelAt_14_14.pth', 'lstm_modelAt_14_21.pth', 'lstm_modelAt_14_28.pth', 'lstm_modelAt_14_7.pth', 'lstm_modelAt_21_14.pth', 'lstm_modelAt_21_21.pth', 'lstm_modelAt_21_28.pth', 'lstm_modelAt_21_7.pth', 'lstm_modelAt_28_14.pth', 'lstm_modelAt_28_21.pth', 'lstm_modelAt_28_28.pth', 'lstm_modelAt_28_7.pth', 'lstm_modelAt_7_14.pth', 'lstm_modelAt_7_21.pth', 'lstm_modelAt_7_28.pth', 'lstm_modelAt_7_7.pth', 'lstm_modelVol7_14_14.pth', 'lstm_modelVol7_14_21.pth', 'lstm_modelVol7_14_28.pth', 'lstm_modelVol7_14_7.pth', 'lstm_modelVol7_21_14.pth', 'lstm_modelVol7_21_21.pth', 'lstm_modelVol7_21_28.pth', 'lstm_modelVol7_21_7.pth', 'lstm_modelVol7_28_14.pth', 'lstm_modelVol7_28_21.pth', 'lstm_modelVol7_28_28.pth', 'lstm_modelVol7_28_7.pth', 'lstm_modelVol7_7_14.pth', 'lstm_modelVol7_7_21.pth', 'lstm_modelVol7_7_28.pth', 'lstm_modelVol7_7_7.pth', 'lstm_model_14_14.pth', 'lstm_model_14_21.pth', 'lstm_model_14_28.pth', 'lstm_model_14_7.pth', 'lstm_model_21_14.pth', 'lstm_model_21_21.pth', 'lstm_model_21_28.pth', 'lstm_model_21_7.pth', 'lstm_model_28_14.pth', 'lstm_model_28_21.pth', 'lstm_model_28_28.pth', 'lstm_model_28_7.pth', 'lstm_model_7_14.pth', 'lstm_model_7_21.pth', 'lstm_model_7_28.pth', 'lstm_model_7_7.pth', 'markdown-notebooks.md', 'markdown.md', 'mlp_model_14_14.pth', 'mlp_model_14_21.pth', 'mlp_model_14_28.pth', 'mlp_model_14_7.pth', 'mlp_model_21_14.pth', 'mlp_model_21_21.pth', 'mlp_model_21_28.pth', 'mlp_model_21_7.pth', 'mlp_model_28_14.pth', 'mlp_model_28_21.pth', 'mlp_model_28_28.pth', 'mlp_model_28_7.pth', 'mlp_model_7_14.pth', 'mlp_model_7_21.pth', 'mlp_model_7_28.pth', 'mlp_model_7_7.pth', 'notebooks.ipynb', 'references.bib', 'requirements.txt', 'rnn_modelAt_14_14.pth', 'rnn_modelAt_14_21.pth', 'rnn_modelAt_14_28.pth', 'rnn_modelAt_14_7.pth', 'rnn_modelAt_21_14.pth', 'rnn_modelAt_21_21.pth', 'rnn_modelAt_21_28.pth', 'rnn_modelAt_21_7.pth', 'rnn_modelAt_28_14.pth', 'rnn_modelAt_28_21.pth', 'rnn_modelAt_28_28.pth', 'rnn_modelAt_28_7.pth', 'rnn_modelAt_7_14.pth', 'rnn_modelAt_7_21.pth', 'rnn_modelAt_7_28.pth', 'rnn_modelAt_7_7.pth', 'rnn_modelVol_14_14.pth', 'rnn_modelVol_14_21.pth', 'rnn_modelVol_14_28.pth', 'rnn_modelVol_14_7.pth', 'rnn_modelVol_21_14.pth', 'rnn_modelVol_21_21.pth', 'rnn_modelVol_21_28.pth', 'rnn_modelVol_21_7.pth', 'rnn_modelVol_28_14.pth', 'rnn_modelVol_28_21.pth', 'rnn_modelVol_28_28.pth', 'rnn_modelVol_28_7.pth', 'rnn_modelVol_7_14.pth', 'rnn_modelVol_7_21.pth', 'rnn_modelVol_7_28.pth', 'rnn_modelVol_7_7.pth', 'rnn_model_14_14.pth', 'rnn_model_14_21.pth', 'rnn_model_14_28.pth', 'rnn_model_14_7.pth', 'rnn_model_21_14.pth', 'rnn_model_21_21.pth', 'rnn_model_21_28.pth', 'rnn_model_21_7.pth', 'rnn_model_28_14.pth', 'rnn_model_28_21.pth', 'rnn_model_28_28.pth', 'rnn_model_28_7.pth', 'rnn_model_7_14.pth', 'rnn_model_7_21.pth', 'rnn_model_7_28.pth', 'rnn_model_7_7.pth', '_build', '_config.yml', '_toc.yml']
Device: cpu
Window Size: 7, Training Step Size: 7
Window Size: 7, Training Step Size: 14
Window Size: 7, Training Step Size: 21
Window Size: 7, Training Step Size: 28
Window Size: 14, Training Step Size: 7
Window Size: 14, Training Step Size: 14
Window Size: 14, Training Step Size: 21
Window Size: 14, Training Step Size: 28
Window Size: 21, Training Step Size: 7
Window Size: 21, Training Step Size: 14
Window Size: 21, Training Step Size: 21
Window Size: 21, Training Step Size: 28
Window Size: 28, Training Step Size: 7
Window Size: 28, Training Step Size: 14
Window Size: 28, Training Step Size: 21
Window Size: 28, Training Step Size: 28
def evaluate_model_residues(model, test_loader, criterion):
model.eval()
all_predictions = []
all_targets = []
with torch.no_grad():
for X_batch, y_batch in test_loader:
X_batch, y_batch = X_batch.to(device), y_batch.to(device)
outputs = model(X_batch)
all_predictions.append(outputs)
all_targets.append(y_batch)
# Concatenación y verificación de dimensiones
all_predictions = torch.cat(all_predictions, dim=0).cpu().numpy().reshape(-1)
all_targets = torch.cat(all_targets, dim=0).cpu().numpy().reshape(-1)
# Imprimir las longitudes para ver si coinciden
print(f"Dimensiones de all_predictions: {all_predictions.shape}")
print(f"Dimensiones de all_targets: {all_targets.shape}")
# Verificar si las dimensiones coinciden antes de realizar operaciones
if all_targets.shape != all_predictions.shape:
raise ValueError(f"Dimensiones no coinciden: all_targets {all_targets.shape}, all_predictions {all_predictions.shape}")
# Calcular residuos
residues = all_targets - all_predictions
# Calcular métricas
mape = mean_absolute_percentage_error(all_targets, all_predictions)
rmse = np.sqrt(mean_squared_error(all_targets, all_predictions))
r2 = r2_score(all_targets, all_predictions)
r2_adj = 1 - (1 - r2) * (len(all_targets) - 1) / (len(all_targets) - window_size - 1)
results_df = pd.DataFrame({
'MAPE': [mape],
'RMSE': [rmse],
'R²': [r2],
'R² adj': [r2_adj]
})
return residues, results_df
import os
import torch
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.metrics import mean_absolute_percentage_error, mean_squared_error, r2_score
from torch.utils.data import DataLoader
mlp_result_df_filtered = mlp_result_df.dropna(subset=['R² adj'])
if mlp_result_df_filtered.empty:
raise ValueError("No hay modelos con valores válidos de 'R² adj'.")
else:
model_with_max_r2_adj = mlp_result_df_filtered.loc[mlp_result_df_filtered['R² adj'].idxmax(), 'Model']
print(f"Model with the largest R² adjusted: {model_with_max_r2_adj}")
# Cargar el mejor modelo
checkpoint = torch.load(os.path.join(shared_folder_path, model_with_max_r2_adj))
input_size = checkpoint['input_size']
hidden_size = checkpoint['hidden_size']
output_size = checkpoint['output_size']
num_layers = checkpoint['num_layers']
test_loader = checkpoint['test_loader']
# Definir el modelo y cargar los parámetros
rnn_model = RNNModel(input_size, hidden_size, output_size, num_layers)
rnn_model.load_state_dict(checkpoint['state_dict'])
rnn_model.to(device)
print("Model loaded successfully!")
# Evaluación del modelo
def evaluate_model_residues(model, test_loader, criterion):
model.eval()
all_predictions = []
all_targets = []
with torch.no_grad():
for X_batch, y_batch in test_loader:
X_batch, y_batch = X_batch.to(device), y_batch.to(device)
outputs = model(X_batch)
all_predictions.append(outputs)
all_targets.append(y_batch)
# Concatenar todas las predicciones y objetivos
all_predictions = torch.cat(all_predictions, dim=0).cpu().numpy().reshape(-1)
all_targets = torch.cat(all_targets, dim=0).cpu().numpy().reshape(-1)
# Asegurar que las longitudes coincidan
min_length = min(len(all_targets), len(all_predictions))
all_targets = all_targets[:min_length]
all_predictions = all_predictions[:min_length]
# Calcular residuos
residues = all_targets - all_predictions
# Calcular métricas
mape = mean_absolute_percentage_error(all_targets, all_predictions)
rmse = np.sqrt(mean_squared_error(all_targets, all_predictions))
r2 = r2_score(all_targets, all_predictions)
r2_adj = 1 - (1 - r2) * (len(all_targets) - 1) / (len(all_targets) - input_size - 1)
results_df = pd.DataFrame({
'MAPE': [mape],
'RMSE': [rmse],
'R²': [r2],
'R² adj': [r2_adj]
})
return residues, results_df
# Evaluación y visualización
residues, results_df = evaluate_model_residues(rnn_model, test_loader, criterion)
test_predictions, test_targets, test_metrics_df = evaluate_and_create_row(rnn_model, test_loader, criterion, scaler, window_size, "RNN")
# Visualización de las predicciones y los valores reales
plt.plot(test_predictions.reshape(-1), label='Test Predictions')
plt.plot(test_targets.reshape(-1), label='Test Targets')
plt.legend()
plt.show()
# Mostrar el DataFrame con las métricas de evaluación
print(test_metrics_df)
Model with the largest R² adjusted: rnn_model_21_7.pth
Model loaded successfully!
Model MAPE RMSE R² R² adj
0 RNN 24.971811 0.120363 -0.886196 -1.357746
import torch
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm
from sklearn.metrics import mean_absolute_percentage_error, mean_squared_error, r2_score
from scipy.stats import kurtosistest, skewtest, jarque_bera
from statsmodels.tsa.stattools import adfuller, kpss
def count_parameters(model):
return sum(p.numel() for p in model.parameters() if p.requires_grad)
def calculate_adjusted_r2(all_targets, all_predictions, model):
n = len(all_targets)
k = count_parameters(model) # Total number of trainable parameters
r2 = r2_score(all_targets, all_predictions)
r2_adj = 1 - (1 - r2) * (n - 1) / (n - k - 1)
print(f"R²: {r2:.4f}")
print(f"R² adjusted: {r2_adj:.4f}")
print(f"Number of trainable parameters: {k}")
print(f"Number of measures: {n}")
return r2_adj
def evaluate_model_residues(model, data_loader, criterion):
model.eval()
all_predictions = []
all_targets = []
with torch.no_grad():
for X_batch, y_batch in data_loader:
outputs = model(X_batch)
all_predictions.append(outputs)
all_targets.append(y_batch)
# Concatenate all predictions and targets
all_predictions = torch.cat(all_predictions, dim=0).cpu().numpy().reshape(-1)
all_targets = torch.cat(all_targets, dim=0).cpu().numpy().reshape(-1)
# Asegurarse de que las longitudes coincidan
min_length = min(len(all_targets), len(all_predictions))
all_targets = all_targets[:min_length]
all_predictions = all_predictions[:min_length]
residues = all_targets - all_predictions
# Calculate statistics
mape = mean_absolute_percentage_error(all_targets, all_predictions)
rmse = np.sqrt(mean_squared_error(all_targets, all_predictions))
r2 = r2_score(all_targets, all_predictions)
kurt_stat, kurt_p_value = kurtosistest(residues)
skew_stat, skew_p_value = skewtest(residues)
jb_stat, jb_p_value = jarque_bera(residues)
adf_stat, adf_p_value, _, _, _, _ = adfuller(residues)
kpss_stat, kpss_p_value, _, _ = kpss(residues)
results = {
'MAPE': mape,
'RMSE': rmse,
'R²': r2,
'Kurtosis (statistic)': kurt_stat,
'Kurtosis (p-value)': kurt_p_value,
'Skewness (statistic)': skew_stat,
'Skewness (p-value)': skew_p_value,
'Jarque-Bera (statistic)': jb_stat,
'Jarque-Bera (p-value)': jb_p_value,
'Dickey Fuller (statistic)': adf_stat,
'Dickey Fuller (p-value)': adf_p_value,
'KPSS test (statistic)': kpss_stat,
'KPSS test (p-value)': kpss_p_value
}
results_df = pd.DataFrame.from_dict(results, orient='index', columns=['Value'])
# Plotting the time series of residues
plt.figure(figsize=(10, 6))
plt.plot(residues)
plt.title('Time Series of Residues')
plt.xlabel('Time')
plt.ylabel('Residues')
plt.show()
# Plotting the histogram of residues
plt.figure(figsize=(10, 6))
sns.histplot(residues, kde=True)
plt.title('Histogram of Residues')
plt.xlabel('Residues')
plt.ylabel('Frequency')
plt.show()
# Q-Q plot of residues
plt.figure(figsize=(10, 6))
sm.qqplot(residues, line='s')
plt.title('Q-Q Plot of Residues')
plt.show()
# ACF plot of residues
plt.figure(figsize=(10, 6))
sm.graphics.tsa.plot_acf(residues, lags=40)
plt.title('Autocorrelation Function (ACF) of Residues')
plt.show()
return residues, results_df
# Uso de la función para evaluar el modelo RNN
residues, results_df = evaluate_model_residues(rnn_model, test_loader, criterion)
print(results_df)
# Calcular el R² ajustado
adjusted_r2 = calculate_adjusted_r2(test_targets, test_predictions, rnn_model)
<Figure size 1000x600 with 0 Axes>
<Figure size 1000x600 with 0 Axes>
Value
MAPE 0.249718
RMSE 0.120363
R² -0.886196
Kurtosis (statistic) 1.452483
Kurtosis (p-value) 0.146367
Skewness (statistic) -3.511924
Skewness (p-value) 0.000445
Jarque-Bera (statistic) 15.616594
Jarque-Bera (p-value) 0.000406
Dickey Fuller (statistic) -2.615550
Dickey Fuller (p-value) 0.089826
KPSS test (statistic) 0.626704
KPSS test (p-value) 0.020209
R²: -0.8862
R² adjusted: 2.3138
Number of trainable parameters: 341
Number of measures: 141
import matplotlib.pyplot as plt
import pandas as pd
# Asegúrate de que 'lstm_result_df' contiene resultados de cada combinación de `window_size` y `train_steps`
# Añadimos una columna para numerar las corridas de entrenamiento
lstm_result_df['Run'] = range(1, len(lstm_result_df) + 1)
# Selecciona la métrica de los residuos que deseas graficar en el eje Y
# Aquí usaremos 'RMSE' como ejemplo; ajusta si prefieres otra métrica, como 'MAPE'
metric_to_plot = 'RMSE'
plt.figure(figsize=(10, 6))
plt.plot(lstm_result_df['Run'], lstm_result_df[metric_to_plot], marker='o', linestyle='-')
plt.xlabel("Run (Combination of Window Size and Training Step Size)")
plt.ylabel(f"{metric_to_plot} (Residual Score)")
plt.title(f"Error/Score vs Run for RNN Model")
plt.grid()
plt.show()